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,99 @@
1
+ """Data transformation tool for testing JSON/data operations."""
2
+
3
+ from typing import Any
4
+ import json
5
+ from genxai.tools.base import Tool, ToolMetadata, ToolParameter, ToolCategory
6
+
7
+
8
+ class DataTransformerTool(Tool):
9
+ """Tool for JSON and data transformation operations."""
10
+
11
+ def __init__(self):
12
+ """Initialize data transformer tool."""
13
+ metadata = ToolMetadata(
14
+ name="data_transformer",
15
+ description="Transform and manipulate JSON data (keys, values, filter, sort)",
16
+ category=ToolCategory.DATA_PROCESSING,
17
+ tags=["json", "data", "transform", "test"],
18
+ version="1.0.0",
19
+ author="GenXAI",
20
+ )
21
+
22
+ parameters = [
23
+ ToolParameter(
24
+ name="data",
25
+ type="string",
26
+ description="JSON string to transform",
27
+ required=True,
28
+ ),
29
+ ToolParameter(
30
+ name="transformation",
31
+ type="string",
32
+ description="The transformation to apply",
33
+ required=True,
34
+ enum=["keys", "values", "count", "pretty", "minify", "reverse_keys"],
35
+ ),
36
+ ]
37
+
38
+ super().__init__(metadata, parameters)
39
+
40
+ async def _execute(self, **kwargs: Any) -> Any:
41
+ """Execute the data transformation.
42
+
43
+ Args:
44
+ **kwargs: Tool parameters (data, transformation)
45
+
46
+ Returns:
47
+ Result of the transformation
48
+ """
49
+ data_str = kwargs.get("data", "{}")
50
+ transformation = kwargs.get("transformation")
51
+
52
+ try:
53
+ # Parse JSON
54
+ data = json.loads(data_str)
55
+ except json.JSONDecodeError as e:
56
+ raise ValueError(f"Invalid JSON: {str(e)}")
57
+
58
+ if transformation == "keys":
59
+ if isinstance(data, dict):
60
+ result = list(data.keys())
61
+ else:
62
+ raise ValueError("Data must be a JSON object for 'keys' transformation")
63
+
64
+ elif transformation == "values":
65
+ if isinstance(data, dict):
66
+ result = list(data.values())
67
+ else:
68
+ raise ValueError("Data must be a JSON object for 'values' transformation")
69
+
70
+ elif transformation == "count":
71
+ if isinstance(data, dict):
72
+ result = len(data)
73
+ elif isinstance(data, list):
74
+ result = len(data)
75
+ else:
76
+ result = 1
77
+
78
+ elif transformation == "pretty":
79
+ result = json.dumps(data, indent=2, sort_keys=True)
80
+
81
+ elif transformation == "minify":
82
+ result = json.dumps(data, separators=(',', ':'))
83
+
84
+ elif transformation == "reverse_keys":
85
+ if isinstance(data, dict):
86
+ result = {k: v for k, v in reversed(data.items())}
87
+ else:
88
+ raise ValueError("Data must be a JSON object for 'reverse_keys' transformation")
89
+
90
+ else:
91
+ raise ValueError(f"Unknown transformation: {transformation}")
92
+
93
+ return {
94
+ "original": data,
95
+ "transformation": transformation,
96
+ "result": result,
97
+ "original_type": type(data).__name__,
98
+ "result_type": type(result).__name__,
99
+ }
@@ -0,0 +1,82 @@
1
+ """Error generator tool for testing error handling."""
2
+
3
+ from typing import Any
4
+ from genxai.tools.base import Tool, ToolMetadata, ToolParameter, ToolCategory
5
+
6
+
7
+ class ErrorGeneratorTool(Tool):
8
+ """Tool to intentionally generate errors for testing error handling."""
9
+
10
+ def __init__(self):
11
+ """Initialize error generator tool."""
12
+ metadata = ToolMetadata(
13
+ name="error_generator",
14
+ description="Generate different types of errors to test error handling",
15
+ category=ToolCategory.SYSTEM,
16
+ tags=["error", "test", "validation"],
17
+ version="1.0.0",
18
+ author="GenXAI",
19
+ )
20
+
21
+ parameters = [
22
+ ToolParameter(
23
+ name="error_type",
24
+ type="string",
25
+ description="Type of error to generate",
26
+ required=True,
27
+ enum=["value_error", "type_error", "zero_division", "key_error", "index_error", "none"],
28
+ ),
29
+ ToolParameter(
30
+ name="message",
31
+ type="string",
32
+ description="Custom error message",
33
+ required=False,
34
+ default="Test error",
35
+ ),
36
+ ]
37
+
38
+ super().__init__(metadata, parameters)
39
+
40
+ async def _execute(self, **kwargs: Any) -> Any:
41
+ """Execute the error generation.
42
+
43
+ Args:
44
+ **kwargs: Tool parameters (error_type, message)
45
+
46
+ Returns:
47
+ Result or raises an error based on error_type
48
+ """
49
+ error_type = kwargs.get("error_type")
50
+ message = kwargs.get("message", "Test error")
51
+
52
+ if error_type == "value_error":
53
+ raise ValueError(f"ValueError: {message}")
54
+
55
+ elif error_type == "type_error":
56
+ raise TypeError(f"TypeError: {message}")
57
+
58
+ elif error_type == "zero_division":
59
+ # Intentionally divide by zero
60
+ result = 1 / 0
61
+ return result
62
+
63
+ elif error_type == "key_error":
64
+ # Access non-existent key
65
+ data = {"key1": "value1"}
66
+ return data["non_existent_key"]
67
+
68
+ elif error_type == "index_error":
69
+ # Access out of bounds index
70
+ data = [1, 2, 3]
71
+ return data[10]
72
+
73
+ elif error_type == "none":
74
+ # No error, return success
75
+ return {
76
+ "status": "success",
77
+ "message": "No error generated",
78
+ "error_type": error_type,
79
+ }
80
+
81
+ else:
82
+ raise ValueError(f"Unknown error type: {error_type}")
@@ -0,0 +1,94 @@
1
+ """Simple math tool for testing basic operations."""
2
+
3
+ from typing import Any
4
+ from genxai.tools.base import Tool, ToolMetadata, ToolParameter, ToolCategory
5
+
6
+
7
+ class SimpleMathTool(Tool):
8
+ """Tool for basic arithmetic operations."""
9
+
10
+ def __init__(self):
11
+ """Initialize simple math tool."""
12
+ metadata = ToolMetadata(
13
+ name="simple_math",
14
+ description="Perform basic arithmetic operations (add, subtract, multiply, divide)",
15
+ category=ToolCategory.COMPUTATION,
16
+ tags=["math", "arithmetic", "test"],
17
+ version="1.0.0",
18
+ author="GenXAI",
19
+ )
20
+
21
+ parameters = [
22
+ ToolParameter(
23
+ name="operation",
24
+ type="string",
25
+ description="The operation to perform",
26
+ required=True,
27
+ enum=["add", "subtract", "multiply", "divide"],
28
+ ),
29
+ ToolParameter(
30
+ name="a",
31
+ type="number",
32
+ description="First number",
33
+ required=True,
34
+ ),
35
+ ToolParameter(
36
+ name="b",
37
+ type="number",
38
+ description="Second number",
39
+ required=True,
40
+ ),
41
+ ]
42
+
43
+ super().__init__(metadata, parameters)
44
+
45
+ async def _execute(self, **kwargs: Any) -> Any:
46
+ """Execute the math operation.
47
+
48
+ Args:
49
+ **kwargs: Tool parameters (operation, a, b)
50
+
51
+ Returns:
52
+ Result of the operation
53
+ """
54
+ operation = kwargs.get("operation")
55
+ a = kwargs.get("a")
56
+ b = kwargs.get("b")
57
+
58
+ if operation == "add":
59
+ result = a + b
60
+ elif operation == "subtract":
61
+ result = a - b
62
+ elif operation == "multiply":
63
+ result = a * b
64
+ elif operation == "divide":
65
+ if b == 0:
66
+ raise ValueError("Cannot divide by zero")
67
+ result = a / b
68
+ else:
69
+ raise ValueError(f"Unknown operation: {operation}")
70
+
71
+ return {
72
+ "operation": operation,
73
+ "a": a,
74
+ "b": b,
75
+ "result": result,
76
+ "formula": f"{a} {self._get_operator(operation)} {b} = {result}",
77
+ }
78
+
79
+ def _get_operator(self, operation: str) -> str:
80
+ """Get the operator symbol for an operation.
81
+
82
+ Args:
83
+ operation: Operation name
84
+
85
+ Returns:
86
+ Operator symbol
87
+ """
88
+ operators = {
89
+ "add": "+",
90
+ "subtract": "-",
91
+ "multiply": "×",
92
+ "divide": "÷",
93
+ }
94
+ return operators.get(operation, "?")
@@ -0,0 +1,72 @@
1
+ """String processing tool for testing string operations."""
2
+
3
+ from typing import Any
4
+ from genxai.tools.base import Tool, ToolMetadata, ToolParameter, ToolCategory
5
+
6
+
7
+ class StringProcessorTool(Tool):
8
+ """Tool for string manipulation operations."""
9
+
10
+ def __init__(self):
11
+ """Initialize string processor tool."""
12
+ metadata = ToolMetadata(
13
+ name="string_processor",
14
+ description="Perform string manipulation operations (uppercase, lowercase, reverse, length, capitalize)",
15
+ category=ToolCategory.DATA_PROCESSING,
16
+ tags=["string", "text", "manipulation", "test"],
17
+ version="1.0.0",
18
+ author="GenXAI",
19
+ )
20
+
21
+ parameters = [
22
+ ToolParameter(
23
+ name="text",
24
+ type="string",
25
+ description="The text to process",
26
+ required=True,
27
+ ),
28
+ ToolParameter(
29
+ name="operation",
30
+ type="string",
31
+ description="The operation to perform",
32
+ required=True,
33
+ enum=["uppercase", "lowercase", "reverse", "length", "capitalize", "title"],
34
+ ),
35
+ ]
36
+
37
+ super().__init__(metadata, parameters)
38
+
39
+ async def _execute(self, **kwargs: Any) -> Any:
40
+ """Execute the string operation.
41
+
42
+ Args:
43
+ **kwargs: Tool parameters (text, operation)
44
+
45
+ Returns:
46
+ Result of the operation
47
+ """
48
+ text = kwargs.get("text", "")
49
+ operation = kwargs.get("operation")
50
+
51
+ if operation == "uppercase":
52
+ result = text.upper()
53
+ elif operation == "lowercase":
54
+ result = text.lower()
55
+ elif operation == "reverse":
56
+ result = text[::-1]
57
+ elif operation == "length":
58
+ result = len(text)
59
+ elif operation == "capitalize":
60
+ result = text.capitalize()
61
+ elif operation == "title":
62
+ result = text.title()
63
+ else:
64
+ raise ValueError(f"Unknown operation: {operation}")
65
+
66
+ return {
67
+ "original": text,
68
+ "operation": operation,
69
+ "result": result,
70
+ "original_length": len(text),
71
+ "result_length": len(str(result)),
72
+ }
@@ -0,0 +1,15 @@
1
+ """Web tools for GenXAI."""
2
+
3
+ from genxai.tools.builtin.web.web_scraper import WebScraperTool
4
+ from genxai.tools.builtin.web.api_caller import APICallerTool
5
+ from genxai.tools.builtin.web.url_validator import URLValidatorTool
6
+ from genxai.tools.builtin.web.http_client import HTTPClientTool
7
+ from genxai.tools.builtin.web.html_parser import HTMLParserTool
8
+
9
+ __all__ = [
10
+ "WebScraperTool",
11
+ "APICallerTool",
12
+ "URLValidatorTool",
13
+ "HTTPClientTool",
14
+ "HTMLParserTool",
15
+ ]
@@ -0,0 +1,161 @@
1
+ """API caller tool for making HTTP requests."""
2
+
3
+ from typing import Any, Dict, Optional
4
+ import logging
5
+ import json
6
+
7
+ from genxai.tools.base import Tool, ToolMetadata, ToolParameter, ToolCategory
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class APICallerTool(Tool):
13
+ """Make HTTP API calls with authentication support."""
14
+
15
+ def __init__(self) -> None:
16
+ """Initialize API caller tool."""
17
+ metadata = ToolMetadata(
18
+ name="api_caller",
19
+ description="Call external REST APIs with various HTTP methods and authentication",
20
+ category=ToolCategory.WEB,
21
+ tags=["api", "http", "rest", "request", "client"],
22
+ version="1.0.0",
23
+ )
24
+
25
+ parameters = [
26
+ ToolParameter(
27
+ name="url",
28
+ type="string",
29
+ description="API endpoint URL",
30
+ required=True,
31
+ pattern=r"^https?://",
32
+ ),
33
+ ToolParameter(
34
+ name="method",
35
+ type="string",
36
+ description="HTTP method",
37
+ required=True,
38
+ enum=["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"],
39
+ ),
40
+ ToolParameter(
41
+ name="headers",
42
+ type="object",
43
+ description="HTTP headers as key-value pairs",
44
+ required=False,
45
+ ),
46
+ ToolParameter(
47
+ name="params",
48
+ type="object",
49
+ description="URL query parameters",
50
+ required=False,
51
+ ),
52
+ ToolParameter(
53
+ name="body",
54
+ type="object",
55
+ description="Request body (for POST, PUT, PATCH)",
56
+ required=False,
57
+ ),
58
+ ToolParameter(
59
+ name="timeout",
60
+ type="number",
61
+ description="Request timeout in seconds",
62
+ required=False,
63
+ default=30,
64
+ min_value=1,
65
+ max_value=300,
66
+ ),
67
+ ToolParameter(
68
+ name="follow_redirects",
69
+ type="boolean",
70
+ description="Whether to follow redirects",
71
+ required=False,
72
+ default=True,
73
+ ),
74
+ ]
75
+
76
+ super().__init__(metadata, parameters)
77
+
78
+ async def _execute(
79
+ self,
80
+ url: str,
81
+ method: str,
82
+ headers: Optional[Dict[str, str]] = None,
83
+ params: Optional[Dict[str, Any]] = None,
84
+ body: Optional[Dict[str, Any]] = None,
85
+ timeout: int = 30,
86
+ follow_redirects: bool = True,
87
+ ) -> Dict[str, Any]:
88
+ """Execute API call.
89
+
90
+ Args:
91
+ url: API endpoint URL
92
+ method: HTTP method
93
+ headers: Request headers
94
+ params: Query parameters
95
+ body: Request body
96
+ timeout: Request timeout
97
+ follow_redirects: Follow redirects flag
98
+
99
+ Returns:
100
+ Dictionary containing response data
101
+ """
102
+ try:
103
+ import httpx
104
+ except ImportError:
105
+ raise ImportError(
106
+ "httpx package not installed. Install with: pip install httpx"
107
+ )
108
+
109
+ # Prepare headers
110
+ request_headers = headers or {}
111
+ if body and "Content-Type" not in request_headers:
112
+ request_headers["Content-Type"] = "application/json"
113
+
114
+ # Prepare request
115
+ method = method.upper()
116
+ request_kwargs: Dict[str, Any] = {
117
+ "method": method,
118
+ "url": url,
119
+ "headers": request_headers,
120
+ "params": params,
121
+ "timeout": timeout,
122
+ "follow_redirects": follow_redirects,
123
+ }
124
+
125
+ # Add body for methods that support it
126
+ if method in ["POST", "PUT", "PATCH"] and body:
127
+ if request_headers.get("Content-Type") == "application/json":
128
+ request_kwargs["json"] = body
129
+ else:
130
+ request_kwargs["data"] = body
131
+
132
+ # Make request
133
+ async with httpx.AsyncClient() as client:
134
+ response = await client.request(**request_kwargs)
135
+
136
+ # Parse response
137
+ result: Dict[str, Any] = {
138
+ "status_code": response.status_code,
139
+ "headers": dict(response.headers),
140
+ "url": str(response.url),
141
+ "method": method,
142
+ }
143
+
144
+ # Try to parse JSON response
145
+ content_type = response.headers.get("content-type", "")
146
+ if "application/json" in content_type:
147
+ try:
148
+ result["data"] = response.json()
149
+ except json.JSONDecodeError:
150
+ result["data"] = response.text
151
+ else:
152
+ result["data"] = response.text
153
+
154
+ # Add response metadata
155
+ result["elapsed_ms"] = response.elapsed.total_seconds() * 1000
156
+ result["success"] = 200 <= response.status_code < 300
157
+
158
+ logger.info(
159
+ f"API call to {url} completed with status {response.status_code}"
160
+ )
161
+ return result