jaf-py 2.5.10__py3-none-any.whl → 2.5.11__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.
- jaf/__init__.py +154 -57
- jaf/a2a/__init__.py +42 -21
- jaf/a2a/agent.py +79 -126
- jaf/a2a/agent_card.py +87 -78
- jaf/a2a/client.py +30 -66
- jaf/a2a/examples/client_example.py +12 -12
- jaf/a2a/examples/integration_example.py +38 -47
- jaf/a2a/examples/server_example.py +56 -53
- jaf/a2a/memory/__init__.py +0 -4
- jaf/a2a/memory/cleanup.py +28 -21
- jaf/a2a/memory/factory.py +155 -133
- jaf/a2a/memory/providers/composite.py +21 -26
- jaf/a2a/memory/providers/in_memory.py +89 -83
- jaf/a2a/memory/providers/postgres.py +117 -115
- jaf/a2a/memory/providers/redis.py +128 -121
- jaf/a2a/memory/serialization.py +77 -87
- jaf/a2a/memory/tests/run_comprehensive_tests.py +112 -83
- jaf/a2a/memory/tests/test_cleanup.py +211 -94
- jaf/a2a/memory/tests/test_serialization.py +73 -68
- jaf/a2a/memory/tests/test_stress_concurrency.py +186 -133
- jaf/a2a/memory/tests/test_task_lifecycle.py +138 -120
- jaf/a2a/memory/types.py +91 -53
- jaf/a2a/protocol.py +95 -125
- jaf/a2a/server.py +90 -118
- jaf/a2a/standalone_client.py +30 -43
- jaf/a2a/tests/__init__.py +16 -33
- jaf/a2a/tests/run_tests.py +17 -53
- jaf/a2a/tests/test_agent.py +40 -140
- jaf/a2a/tests/test_client.py +54 -117
- jaf/a2a/tests/test_integration.py +28 -82
- jaf/a2a/tests/test_protocol.py +54 -139
- jaf/a2a/tests/test_types.py +50 -136
- jaf/a2a/types.py +58 -34
- jaf/cli.py +21 -41
- jaf/core/__init__.py +7 -1
- jaf/core/agent_tool.py +93 -72
- jaf/core/analytics.py +257 -207
- jaf/core/checkpoint.py +223 -0
- jaf/core/composition.py +249 -235
- jaf/core/engine.py +817 -519
- jaf/core/errors.py +55 -42
- jaf/core/guardrails.py +276 -202
- jaf/core/handoff.py +47 -31
- jaf/core/parallel_agents.py +69 -75
- jaf/core/performance.py +75 -73
- jaf/core/proxy.py +43 -44
- jaf/core/proxy_helpers.py +24 -27
- jaf/core/regeneration.py +220 -129
- jaf/core/state.py +68 -66
- jaf/core/streaming.py +115 -108
- jaf/core/tool_results.py +111 -101
- jaf/core/tools.py +114 -116
- jaf/core/tracing.py +269 -210
- jaf/core/types.py +371 -151
- jaf/core/workflows.py +209 -168
- jaf/exceptions.py +46 -38
- jaf/memory/__init__.py +1 -6
- jaf/memory/approval_storage.py +54 -77
- jaf/memory/factory.py +4 -4
- jaf/memory/providers/in_memory.py +216 -180
- jaf/memory/providers/postgres.py +216 -146
- jaf/memory/providers/redis.py +173 -116
- jaf/memory/types.py +70 -51
- jaf/memory/utils.py +36 -34
- jaf/plugins/__init__.py +12 -12
- jaf/plugins/base.py +105 -96
- jaf/policies/__init__.py +0 -1
- jaf/policies/handoff.py +37 -46
- jaf/policies/validation.py +76 -52
- jaf/providers/__init__.py +6 -3
- jaf/providers/mcp.py +97 -51
- jaf/providers/model.py +360 -279
- jaf/server/__init__.py +1 -1
- jaf/server/main.py +7 -11
- jaf/server/server.py +514 -359
- jaf/server/types.py +208 -52
- jaf/utils/__init__.py +17 -18
- jaf/utils/attachments.py +111 -116
- jaf/utils/document_processor.py +175 -174
- jaf/visualization/__init__.py +1 -1
- jaf/visualization/example.py +111 -110
- jaf/visualization/functional_core.py +46 -71
- jaf/visualization/graphviz.py +154 -189
- jaf/visualization/imperative_shell.py +7 -16
- jaf/visualization/types.py +8 -4
- {jaf_py-2.5.10.dist-info → jaf_py-2.5.11.dist-info}/METADATA +2 -2
- jaf_py-2.5.11.dist-info/RECORD +97 -0
- jaf_py-2.5.10.dist-info/RECORD +0 -96
- {jaf_py-2.5.10.dist-info → jaf_py-2.5.11.dist-info}/WHEEL +0 -0
- {jaf_py-2.5.10.dist-info → jaf_py-2.5.11.dist-info}/entry_points.txt +0 -0
- {jaf_py-2.5.10.dist-info → jaf_py-2.5.11.dist-info}/licenses/LICENSE +0 -0
- {jaf_py-2.5.10.dist-info → jaf_py-2.5.11.dist-info}/top_level.txt +0 -0
jaf/providers/mcp.py
CHANGED
|
@@ -23,7 +23,8 @@ from fastmcp.client.transports import (
|
|
|
23
23
|
from ..core.tool_results import ToolErrorCodes, ToolResult, ToolResultStatus
|
|
24
24
|
from ..core.types import ToolSchema
|
|
25
25
|
|
|
26
|
-
Ctx = TypeVar(
|
|
26
|
+
Ctx = TypeVar("Ctx")
|
|
27
|
+
|
|
27
28
|
|
|
28
29
|
def _json_schema_to_python_type(schema: Dict[str, Any]) -> type:
|
|
29
30
|
"""Maps JSON schema types to Python types for Pydantic model creation."""
|
|
@@ -65,14 +66,24 @@ def _json_schema_to_python_type(schema: Dict[str, Any]) -> type:
|
|
|
65
66
|
# Fallback
|
|
66
67
|
return Any
|
|
67
68
|
|
|
69
|
+
|
|
68
70
|
class MCPToolArgs(BaseModel):
|
|
69
71
|
"""Base class for MCP tool arguments."""
|
|
72
|
+
|
|
70
73
|
pass
|
|
71
74
|
|
|
75
|
+
|
|
72
76
|
class FastMCPTool:
|
|
73
77
|
"""A tool that proxies to a FastMCP server, managing its own session."""
|
|
74
78
|
|
|
75
|
-
def __init__(
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
transport: Union[StdioTransport, SSETransport, StreamableHttpTransport],
|
|
82
|
+
tool_info: mcp.types.Tool,
|
|
83
|
+
args_model: type[BaseModel],
|
|
84
|
+
client_info: mcp.types.Implementation,
|
|
85
|
+
timeout: Optional[float] = None,
|
|
86
|
+
):
|
|
76
87
|
self.transport = transport
|
|
77
88
|
self.tool_name = tool_info.name
|
|
78
89
|
self.args_model = args_model
|
|
@@ -82,7 +93,7 @@ class FastMCPTool:
|
|
|
82
93
|
name=tool_info.name,
|
|
83
94
|
description=tool_info.description or f"MCP tool: {tool_info.name}",
|
|
84
95
|
parameters=args_model,
|
|
85
|
-
timeout=timeout
|
|
96
|
+
timeout=timeout,
|
|
86
97
|
)
|
|
87
98
|
|
|
88
99
|
@property
|
|
@@ -92,15 +103,15 @@ class FastMCPTool:
|
|
|
92
103
|
def _convert_simple_filters_to_flat_filter(self, simple_filters: dict) -> dict:
|
|
93
104
|
"""
|
|
94
105
|
Converts a "simple filter" dictionary to the "FlatFilter" format required by some tools.
|
|
95
|
-
|
|
106
|
+
|
|
96
107
|
Simple filters are dictionaries mapping field names to values, e.g.:
|
|
97
108
|
{"status": "active", "category": ["A", "B"]}
|
|
98
109
|
Each key is a field name, and each value is either a single value or a list of values to match.
|
|
99
|
-
|
|
110
|
+
|
|
100
111
|
Flat filters are a more structured format with two keys:
|
|
101
112
|
- "clauses": a list of filter conditions, each specifying a field, a condition (e.g., "In"), and a list of values.
|
|
102
113
|
- "logic": a string expressing how to combine the clauses (e.g., "0 AND 1").
|
|
103
|
-
|
|
114
|
+
|
|
104
115
|
Example output:
|
|
105
116
|
{
|
|
106
117
|
"clauses": [
|
|
@@ -109,49 +120,47 @@ class FastMCPTool:
|
|
|
109
120
|
],
|
|
110
121
|
"logic": "0 AND 1"
|
|
111
122
|
}
|
|
112
|
-
|
|
123
|
+
|
|
113
124
|
This conversion is needed for tools that expect filters in FlatFilter format rather than as simple key-value pairs.
|
|
114
|
-
|
|
125
|
+
|
|
115
126
|
Args:
|
|
116
127
|
simple_filters (dict): A dictionary of field names to values (simple filter).
|
|
117
|
-
|
|
128
|
+
|
|
118
129
|
Returns:
|
|
119
130
|
dict: The filter in FlatFilter format.
|
|
120
131
|
"""
|
|
121
132
|
if not simple_filters:
|
|
122
133
|
return simple_filters
|
|
123
|
-
|
|
134
|
+
|
|
124
135
|
clauses = []
|
|
125
136
|
for i, (field, value) in enumerate(simple_filters.items()):
|
|
126
137
|
# Convert single values to lists for "In" condition
|
|
127
138
|
if not isinstance(value, list):
|
|
128
139
|
value = [value]
|
|
129
|
-
|
|
130
|
-
clauses.append({
|
|
131
|
-
|
|
132
|
-
"condition": "In",
|
|
133
|
-
"val": value
|
|
134
|
-
})
|
|
135
|
-
|
|
140
|
+
|
|
141
|
+
clauses.append({"field": field, "condition": "In", "val": value})
|
|
142
|
+
|
|
136
143
|
# Create logic string: "0 AND 1 AND 2..." for all clauses
|
|
137
144
|
logic = " AND ".join(str(i) for i in range(len(clauses)))
|
|
138
|
-
|
|
139
|
-
return {
|
|
140
|
-
"clauses": clauses,
|
|
141
|
-
"logic": logic
|
|
142
|
-
}
|
|
145
|
+
|
|
146
|
+
return {"clauses": clauses, "logic": logic}
|
|
143
147
|
|
|
144
148
|
def _transform_arguments_for_tool(self, args_dict: dict) -> dict:
|
|
145
149
|
"""Transform arguments based on tool-specific requirements."""
|
|
146
150
|
# Handle flatFilters transformation for tools that expect FlatFilter schema
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
if (
|
|
152
|
+
"flatFilters" in args_dict
|
|
153
|
+
and isinstance(args_dict["flatFilters"], dict)
|
|
154
|
+
and "clauses" not in args_dict["flatFilters"]
|
|
155
|
+
):
|
|
156
|
+
logging.info(
|
|
157
|
+
f"[JAF MCP] Converting simple flatFilters to FlatFilter format for {self.tool_name}"
|
|
158
|
+
)
|
|
159
|
+
args_dict["flatFilters"] = self._convert_simple_filters_to_flat_filter(
|
|
160
|
+
args_dict["flatFilters"]
|
|
161
|
+
)
|
|
153
162
|
logging.info(f"[JAF MCP] Converted flatFilters: {args_dict['flatFilters']}")
|
|
154
|
-
|
|
163
|
+
|
|
155
164
|
return args_dict
|
|
156
165
|
|
|
157
166
|
async def execute(self, args: MCPToolArgs, context: Ctx) -> ToolResult:
|
|
@@ -160,14 +169,15 @@ class FastMCPTool:
|
|
|
160
169
|
async with client:
|
|
161
170
|
# Only include fields that were explicitly set, not defaults
|
|
162
171
|
args_dict = args.model_dump(exclude_none=True, exclude_unset=True)
|
|
163
|
-
|
|
172
|
+
|
|
164
173
|
# Apply tool-specific argument transformations
|
|
165
174
|
args_dict = self._transform_arguments_for_tool(args_dict)
|
|
166
|
-
|
|
175
|
+
|
|
167
176
|
result = await client.call_tool_mcp(self.tool_name, arguments=args_dict)
|
|
168
177
|
|
|
169
178
|
if result.isError:
|
|
170
179
|
from ..core.tool_results import ToolErrorInfo
|
|
180
|
+
|
|
171
181
|
error_message = "MCP tool execution failed"
|
|
172
182
|
if result.content and isinstance(result.content[0], mcp.types.TextContent):
|
|
173
183
|
error_message = result.content[0].text
|
|
@@ -176,34 +186,40 @@ class FastMCPTool:
|
|
|
176
186
|
error=ToolErrorInfo(
|
|
177
187
|
code=ToolErrorCodes.EXECUTION_FAILED,
|
|
178
188
|
message=error_message,
|
|
179
|
-
details={"mcp_error": result.structuredContent}
|
|
180
|
-
)
|
|
189
|
+
details={"mcp_error": result.structuredContent},
|
|
190
|
+
),
|
|
181
191
|
)
|
|
182
|
-
|
|
192
|
+
|
|
183
193
|
data = result.structuredContent if result.structuredContent else str(result.content)
|
|
184
|
-
|
|
194
|
+
|
|
185
195
|
# Create proper ToolMetadata object
|
|
186
196
|
from ..core.tool_results import ToolMetadata
|
|
197
|
+
|
|
187
198
|
tool_metadata = ToolMetadata(extra={"mcp_response": str(result)})
|
|
188
|
-
|
|
199
|
+
|
|
189
200
|
return ToolResult(
|
|
190
|
-
status=ToolResultStatus.SUCCESS,
|
|
191
|
-
data=str(data),
|
|
192
|
-
metadata=tool_metadata
|
|
201
|
+
status=ToolResultStatus.SUCCESS, data=str(data), metadata=tool_metadata
|
|
193
202
|
)
|
|
194
203
|
|
|
195
204
|
except Exception as e:
|
|
196
205
|
from ..core.tool_results import ToolErrorInfo
|
|
206
|
+
|
|
197
207
|
return ToolResult(
|
|
198
208
|
status=ToolResultStatus.ERROR,
|
|
199
209
|
error=ToolErrorInfo(
|
|
200
210
|
code=ToolErrorCodes.EXECUTION_FAILED,
|
|
201
211
|
message=f"MCP tool execution failed: {e!s}",
|
|
202
|
-
details={"error": str(e)}
|
|
203
|
-
)
|
|
212
|
+
details={"error": str(e)},
|
|
213
|
+
),
|
|
204
214
|
)
|
|
205
215
|
|
|
206
|
-
|
|
216
|
+
|
|
217
|
+
async def create_tools_from_transport(
|
|
218
|
+
transport: Union[StdioTransport, SSETransport, StreamableHttpTransport],
|
|
219
|
+
client_info: mcp.types.Implementation,
|
|
220
|
+
extra_fields: Optional[Dict[str, Any]] = None,
|
|
221
|
+
default_timeout: Optional[float] = None,
|
|
222
|
+
) -> List[FastMCPTool]:
|
|
207
223
|
"""Create JAF tools from an MCP transport."""
|
|
208
224
|
client = Client(transport, client_info=client_info)
|
|
209
225
|
tools = []
|
|
@@ -216,11 +232,18 @@ async def create_tools_from_transport(transport: Union[StdioTransport, SSETransp
|
|
|
216
232
|
# Support both inputSchema (camelCase) and input_schema (snake_case) for compatibility with different MCP implementations.
|
|
217
233
|
camel_schema = getattr(tool_info, "inputSchema", None)
|
|
218
234
|
snake_schema = getattr(tool_info, "input_schema", None)
|
|
219
|
-
|
|
235
|
+
|
|
220
236
|
# Choose schema with preference for camelCase when both are non-empty dicts
|
|
221
237
|
if camel_schema and snake_schema:
|
|
222
|
-
if
|
|
223
|
-
|
|
238
|
+
if (
|
|
239
|
+
isinstance(camel_schema, dict)
|
|
240
|
+
and isinstance(snake_schema, dict)
|
|
241
|
+
and camel_schema
|
|
242
|
+
and snake_schema
|
|
243
|
+
):
|
|
244
|
+
logging.info(
|
|
245
|
+
f"Both 'inputSchema' and 'input_schema' are present for tool '{tool_info.name}'; preferring 'inputSchema'."
|
|
246
|
+
)
|
|
224
247
|
params_schema = camel_schema
|
|
225
248
|
elif camel_schema:
|
|
226
249
|
params_schema = camel_schema
|
|
@@ -232,7 +255,7 @@ async def create_tools_from_transport(transport: Union[StdioTransport, SSETransp
|
|
|
232
255
|
params_schema = snake_schema
|
|
233
256
|
else:
|
|
234
257
|
params_schema = {}
|
|
235
|
-
|
|
258
|
+
|
|
236
259
|
# Ensure params_schema is a dict before accessing .get
|
|
237
260
|
params_schema = params_schema or {}
|
|
238
261
|
properties = params_schema.get("properties", {})
|
|
@@ -246,7 +269,7 @@ async def create_tools_from_transport(transport: Union[StdioTransport, SSETransp
|
|
|
246
269
|
else:
|
|
247
270
|
# Don't set default values - let them be None so exclude_unset works
|
|
248
271
|
fields[param_name] = (Optional[param_type], None)
|
|
249
|
-
|
|
272
|
+
|
|
250
273
|
# Add any extra fields to all tool schemas if not already present
|
|
251
274
|
if extra_fields:
|
|
252
275
|
for field_name, field_type in extra_fields.items():
|
|
@@ -258,12 +281,21 @@ async def create_tools_from_transport(transport: Union[StdioTransport, SSETransp
|
|
|
258
281
|
**fields,
|
|
259
282
|
__base__=MCPToolArgs,
|
|
260
283
|
)
|
|
261
|
-
tools.append(
|
|
284
|
+
tools.append(
|
|
285
|
+
FastMCPTool(transport, tool_info, ArgsModel, client_info, default_timeout)
|
|
286
|
+
)
|
|
262
287
|
except Exception as e:
|
|
263
288
|
logging.error(f"Failed to create MCP tools: {e}")
|
|
264
289
|
return tools
|
|
265
290
|
|
|
266
|
-
|
|
291
|
+
|
|
292
|
+
async def create_mcp_stdio_tools(
|
|
293
|
+
command: List[str],
|
|
294
|
+
client_name: str = "JAF",
|
|
295
|
+
client_version: str = "2.0.0",
|
|
296
|
+
extra_fields: Optional[Dict[str, Any]] = None,
|
|
297
|
+
default_timeout: Optional[float] = None,
|
|
298
|
+
) -> List[FastMCPTool]:
|
|
267
299
|
if not command:
|
|
268
300
|
raise ValueError("Command list must not be empty for MCP stdio transport.")
|
|
269
301
|
# Add juspay_meta_info by default for backward compatibility
|
|
@@ -273,7 +305,14 @@ async def create_mcp_stdio_tools(command: List[str], client_name: str = "JAF", c
|
|
|
273
305
|
client_info = mcp.types.Implementation(name=client_name, version=client_version)
|
|
274
306
|
return await create_tools_from_transport(transport, client_info, extra_fields, default_timeout)
|
|
275
307
|
|
|
276
|
-
|
|
308
|
+
|
|
309
|
+
async def create_mcp_sse_tools(
|
|
310
|
+
uri: str,
|
|
311
|
+
client_name: str = "JAF",
|
|
312
|
+
client_version: str = "2.0.0",
|
|
313
|
+
extra_fields: Optional[Dict[str, Any]] = None,
|
|
314
|
+
default_timeout: Optional[float] = None,
|
|
315
|
+
) -> List[FastMCPTool]:
|
|
277
316
|
# Add juspay_meta_info by default for backward compatibility
|
|
278
317
|
if extra_fields is None:
|
|
279
318
|
extra_fields = {"juspay_meta_info": Dict[str, Any]}
|
|
@@ -281,7 +320,14 @@ async def create_mcp_sse_tools(uri: str, client_name: str = "JAF", client_versio
|
|
|
281
320
|
client_info = mcp.types.Implementation(name=client_name, version=client_version)
|
|
282
321
|
return await create_tools_from_transport(transport, client_info, extra_fields, default_timeout)
|
|
283
322
|
|
|
284
|
-
|
|
323
|
+
|
|
324
|
+
async def create_mcp_http_tools(
|
|
325
|
+
uri: str,
|
|
326
|
+
client_name: str = "JAF",
|
|
327
|
+
client_version: str = "2.0.0",
|
|
328
|
+
extra_fields: Optional[Dict[str, Any]] = None,
|
|
329
|
+
default_timeout: Optional[float] = None,
|
|
330
|
+
) -> List[FastMCPTool]:
|
|
285
331
|
# Add juspay_meta_info by default for backward compatibility
|
|
286
332
|
if extra_fields is None:
|
|
287
333
|
extra_fields = {"juspay_meta_info": Dict[str, Any]}
|