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/core/tools.py
CHANGED
|
@@ -30,28 +30,28 @@ from .tool_results import ToolResult
|
|
|
30
30
|
def create_function_tool(config: FunctionToolConfig) -> Tool:
|
|
31
31
|
"""
|
|
32
32
|
Create a function-based tool using object configuration.
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
This is the new, recommended API for creating tools that provides better
|
|
35
35
|
type safety, extensibility, and self-documentation.
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
Args:
|
|
38
38
|
config: Tool configuration object with name, description, execute function,
|
|
39
39
|
parameters, and optional metadata and source.
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
Returns:
|
|
42
42
|
A Tool implementation that can be used with agents.
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
Example:
|
|
45
45
|
```python
|
|
46
46
|
from pydantic import BaseModel
|
|
47
47
|
from jaf import create_function_tool, ToolSource
|
|
48
|
-
|
|
48
|
+
|
|
49
49
|
class GreetArgs(BaseModel):
|
|
50
50
|
name: str
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
async def greet_execute(args: GreetArgs, context) -> str:
|
|
53
53
|
return f"Hello, {args.name}!"
|
|
54
|
-
|
|
54
|
+
|
|
55
55
|
greet_tool = create_function_tool({
|
|
56
56
|
'name': 'greet',
|
|
57
57
|
'description': 'Greets a user by name',
|
|
@@ -63,83 +63,85 @@ def create_function_tool(config: FunctionToolConfig) -> Tool:
|
|
|
63
63
|
```
|
|
64
64
|
"""
|
|
65
65
|
# Get the function from config
|
|
66
|
-
original_func = config[
|
|
67
|
-
|
|
66
|
+
original_func = config["execute"]
|
|
67
|
+
|
|
68
68
|
# Validate tool configuration
|
|
69
69
|
logger = logging.getLogger(__name__)
|
|
70
|
-
|
|
71
|
-
tool_name = config[
|
|
72
|
-
parameters = config[
|
|
73
|
-
|
|
70
|
+
|
|
71
|
+
tool_name = config["name"]
|
|
72
|
+
parameters = config["parameters"]
|
|
73
|
+
|
|
74
74
|
logger.info(f"Creating tool: {tool_name}")
|
|
75
|
-
|
|
75
|
+
|
|
76
76
|
# Validate parameters schema
|
|
77
77
|
if parameters is None:
|
|
78
78
|
logger.error(f"Tool {tool_name}: parameters is None - LLM will receive no schema!")
|
|
79
79
|
raise ValueError(f"Tool '{tool_name}' has None parameters. Provide a Pydantic model class.")
|
|
80
|
-
|
|
80
|
+
|
|
81
81
|
# Check if it's a Pydantic model class
|
|
82
82
|
if BaseModel is None:
|
|
83
83
|
logger.warning(f"Pydantic not available for tool {tool_name} validation")
|
|
84
84
|
else:
|
|
85
85
|
if not (isinstance(parameters, type) and issubclass(parameters, BaseModel)):
|
|
86
|
-
logger.error(
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
logger.error(
|
|
87
|
+
f"Tool {tool_name}: parameters must be a Pydantic BaseModel class, got {type(parameters)}"
|
|
88
|
+
)
|
|
89
|
+
raise ValueError(
|
|
90
|
+
f"Tool '{tool_name}' parameters must be a Pydantic BaseModel class, got {type(parameters)}"
|
|
91
|
+
)
|
|
92
|
+
|
|
89
93
|
# Validate schema generation (cached for performance)
|
|
90
|
-
if not hasattr(parameters,
|
|
94
|
+
if not hasattr(parameters, "_schema_validated"):
|
|
91
95
|
try:
|
|
92
96
|
# Generate schema once to validate the model is well-formed.
|
|
93
97
|
# Allow empty object schemas (no parameters) for tools that take no args.
|
|
94
|
-
if hasattr(parameters,
|
|
98
|
+
if hasattr(parameters, "model_json_schema"):
|
|
95
99
|
_ = parameters.model_json_schema()
|
|
96
|
-
elif hasattr(parameters,
|
|
100
|
+
elif hasattr(parameters, "schema"):
|
|
97
101
|
_ = parameters.schema()
|
|
98
102
|
parameters._schema_validated = True
|
|
99
103
|
except Exception as e:
|
|
100
104
|
logger.error(f"Tool {tool_name} schema generation failed: {e}")
|
|
101
105
|
raise ValueError(f"Tool '{tool_name}' schema generation failed: {e}")
|
|
102
|
-
|
|
106
|
+
|
|
103
107
|
# Create schema
|
|
104
108
|
tool_schema = ToolSchema(
|
|
105
|
-
name=config[
|
|
106
|
-
description=config[
|
|
107
|
-
parameters=config[
|
|
108
|
-
timeout=config.get(
|
|
109
|
+
name=config["name"],
|
|
110
|
+
description=config["description"],
|
|
111
|
+
parameters=config["parameters"],
|
|
112
|
+
timeout=config.get("timeout"),
|
|
109
113
|
)
|
|
110
|
-
|
|
114
|
+
|
|
111
115
|
# Create a new wrapper function for this tool to avoid conflicts when multiple tools use the same base function
|
|
112
116
|
async def tool_wrapper(args: Any, context: Any) -> Union[str, ToolResult]:
|
|
113
117
|
"""Execute the tool with given arguments and context."""
|
|
114
118
|
result = original_func(args, context)
|
|
115
|
-
|
|
119
|
+
|
|
116
120
|
# Handle both sync and async execute functions
|
|
117
|
-
if hasattr(result,
|
|
121
|
+
if hasattr(result, "__await__"):
|
|
118
122
|
return await result
|
|
119
123
|
return result
|
|
120
|
-
|
|
124
|
+
|
|
121
125
|
# Add tool properties and methods to the wrapper function
|
|
122
126
|
tool_wrapper.schema = tool_schema
|
|
123
|
-
tool_wrapper.metadata = config.get(
|
|
124
|
-
tool_wrapper.source = config.get(
|
|
125
|
-
|
|
127
|
+
tool_wrapper.metadata = config.get("metadata", {})
|
|
128
|
+
tool_wrapper.source = config.get("source", ToolSource.NATIVE)
|
|
129
|
+
|
|
126
130
|
# Add execute method that calls the wrapper function
|
|
127
131
|
async def execute(args: Any, context: Any) -> Union[str, ToolResult]:
|
|
128
132
|
"""Execute the tool with given arguments and context."""
|
|
129
133
|
return await tool_wrapper(args, context)
|
|
130
|
-
|
|
134
|
+
|
|
131
135
|
tool_wrapper.execute = execute
|
|
132
|
-
|
|
136
|
+
|
|
133
137
|
# Add __call__ method for direct execution
|
|
134
138
|
async def call_method(args: Any, context: Any) -> Union[str, ToolResult]:
|
|
135
139
|
"""Execute the tool with given arguments and context."""
|
|
136
140
|
return await tool_wrapper.execute(args, context)
|
|
137
|
-
|
|
138
|
-
tool_wrapper.__call__ = call_method
|
|
139
|
-
|
|
140
|
-
return tool_wrapper
|
|
141
141
|
|
|
142
|
+
tool_wrapper.__call__ = call_method
|
|
142
143
|
|
|
144
|
+
return tool_wrapper
|
|
143
145
|
|
|
144
146
|
|
|
145
147
|
def create_function_tool_legacy(
|
|
@@ -148,14 +150,14 @@ def create_function_tool_legacy(
|
|
|
148
150
|
execute: ToolExecuteFunction,
|
|
149
151
|
parameters: Any,
|
|
150
152
|
metadata: Optional[Dict[str, Any]] = None,
|
|
151
|
-
source: Optional[ToolSource] = None
|
|
153
|
+
source: Optional[ToolSource] = None,
|
|
152
154
|
) -> Tool:
|
|
153
155
|
"""
|
|
154
156
|
Create a function-based tool using legacy positional arguments.
|
|
155
|
-
|
|
157
|
+
|
|
156
158
|
**DEPRECATED**: This function is deprecated. Use `create_function_tool` with
|
|
157
159
|
an object-based configuration instead for better type safety and extensibility.
|
|
158
|
-
|
|
160
|
+
|
|
159
161
|
Args:
|
|
160
162
|
name: The name of the tool
|
|
161
163
|
description: A description of what the tool does
|
|
@@ -163,22 +165,22 @@ def create_function_tool_legacy(
|
|
|
163
165
|
parameters: Pydantic model or similar for parameter validation
|
|
164
166
|
metadata: Optional metadata for the tool
|
|
165
167
|
source: Optional source tracking for the tool
|
|
166
|
-
|
|
168
|
+
|
|
167
169
|
Returns:
|
|
168
170
|
A Tool implementation that can be used with agents.
|
|
169
171
|
"""
|
|
170
172
|
warnings.warn(
|
|
171
173
|
"create_function_tool_legacy is deprecated. Use create_function_tool with object configuration instead.",
|
|
172
174
|
DeprecationWarning,
|
|
173
|
-
stacklevel=2
|
|
175
|
+
stacklevel=2,
|
|
174
176
|
)
|
|
175
177
|
config: FunctionToolConfig = {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
178
|
+
"name": name,
|
|
179
|
+
"description": description,
|
|
180
|
+
"execute": execute,
|
|
181
|
+
"parameters": parameters,
|
|
182
|
+
"metadata": metadata,
|
|
183
|
+
"source": source or ToolSource.NATIVE,
|
|
182
184
|
}
|
|
183
185
|
return create_function_tool(config)
|
|
184
186
|
|
|
@@ -186,13 +188,13 @@ def create_function_tool_legacy(
|
|
|
186
188
|
def create_async_function_tool(config: FunctionToolConfig) -> Tool:
|
|
187
189
|
"""
|
|
188
190
|
Create an async function-based tool using object configuration.
|
|
189
|
-
|
|
191
|
+
|
|
190
192
|
This is a convenience function that's identical to create_function_tool
|
|
191
193
|
but with a name that makes it clear the execute function should be async.
|
|
192
|
-
|
|
194
|
+
|
|
193
195
|
Args:
|
|
194
196
|
config: Tool configuration object with async execute function.
|
|
195
|
-
|
|
197
|
+
|
|
196
198
|
Returns:
|
|
197
199
|
A Tool implementation that can be used with agents.
|
|
198
200
|
"""
|
|
@@ -205,18 +207,18 @@ def create_async_function_tool_legacy(
|
|
|
205
207
|
execute: ToolExecuteFunction,
|
|
206
208
|
parameters: Any,
|
|
207
209
|
metadata: Optional[Dict[str, Any]] = None,
|
|
208
|
-
source: Optional[ToolSource] = None
|
|
210
|
+
source: Optional[ToolSource] = None,
|
|
209
211
|
) -> Tool:
|
|
210
212
|
"""
|
|
211
213
|
Create an async function-based tool using legacy positional arguments.
|
|
212
|
-
|
|
214
|
+
|
|
213
215
|
**DEPRECATED**: This function is deprecated. Use `create_function_tool` with
|
|
214
216
|
an object-based configuration instead for better type safety and extensibility.
|
|
215
217
|
"""
|
|
216
218
|
warnings.warn(
|
|
217
219
|
"create_async_function_tool_legacy is deprecated. Use create_function_tool with object configuration instead.",
|
|
218
220
|
DeprecationWarning,
|
|
219
|
-
stacklevel=2
|
|
221
|
+
stacklevel=2,
|
|
220
222
|
)
|
|
221
223
|
return create_function_tool_legacy(name, description, execute, parameters, metadata, source)
|
|
222
224
|
|
|
@@ -225,32 +227,34 @@ def _extract_docstring_info(func):
|
|
|
225
227
|
"""Extract description and parameter info from function docstring."""
|
|
226
228
|
doc = inspect.getdoc(func)
|
|
227
229
|
if not doc:
|
|
228
|
-
return func.__name__.replace(
|
|
229
|
-
|
|
230
|
-
lines = doc.strip().split(
|
|
230
|
+
return func.__name__.replace("_", " ").title(), {}
|
|
231
|
+
|
|
232
|
+
lines = doc.strip().split("\n")
|
|
231
233
|
if not lines:
|
|
232
|
-
return func.__name__.replace(
|
|
233
|
-
|
|
234
|
+
return func.__name__.replace("_", " ").title(), {}
|
|
235
|
+
|
|
234
236
|
# First non-empty line is the description
|
|
235
237
|
description = lines[0].strip()
|
|
236
|
-
|
|
238
|
+
|
|
237
239
|
# Look for Args section to extract parameter descriptions
|
|
238
240
|
param_descriptions = {}
|
|
239
241
|
in_args_section = False
|
|
240
|
-
|
|
242
|
+
|
|
241
243
|
for line in lines[1:]:
|
|
242
244
|
line = line.strip()
|
|
243
|
-
if line.lower().startswith(
|
|
245
|
+
if line.lower().startswith("args:"):
|
|
244
246
|
in_args_section = True
|
|
245
247
|
continue
|
|
246
|
-
elif line.lower().startswith(
|
|
248
|
+
elif line.lower().startswith(
|
|
249
|
+
("returns:", "return:", "raises:", "raise:", "examples:", "example:")
|
|
250
|
+
):
|
|
247
251
|
in_args_section = False
|
|
248
252
|
continue
|
|
249
|
-
elif in_args_section and line and
|
|
253
|
+
elif in_args_section and line and ":" in line:
|
|
250
254
|
# Parse parameter description like "location: The location to fetch the weather for."
|
|
251
|
-
param_name, param_desc = line.split(
|
|
255
|
+
param_name, param_desc = line.split(":", 1)
|
|
252
256
|
param_descriptions[param_name.strip()] = param_desc.strip()
|
|
253
|
-
|
|
257
|
+
|
|
254
258
|
return description, param_descriptions
|
|
255
259
|
|
|
256
260
|
|
|
@@ -259,45 +263,45 @@ def _create_parameter_schema_from_signature(func):
|
|
|
259
263
|
try:
|
|
260
264
|
# Try to use Pydantic if available
|
|
261
265
|
from pydantic import BaseModel, create_model
|
|
262
|
-
|
|
266
|
+
|
|
263
267
|
signature = inspect.signature(func)
|
|
264
268
|
type_hints = get_type_hints(func)
|
|
265
|
-
|
|
269
|
+
|
|
266
270
|
# Extract parameter info, excluding 'context' parameter
|
|
267
271
|
fields = {}
|
|
268
272
|
for param_name, param in signature.parameters.items():
|
|
269
|
-
if param_name ==
|
|
273
|
+
if param_name == "context":
|
|
270
274
|
continue
|
|
271
|
-
|
|
275
|
+
|
|
272
276
|
param_type = type_hints.get(param_name, str)
|
|
273
|
-
|
|
277
|
+
|
|
274
278
|
# Handle default values
|
|
275
279
|
if param.default != inspect.Parameter.empty:
|
|
276
280
|
fields[param_name] = (param_type, param.default)
|
|
277
281
|
else:
|
|
278
282
|
fields[param_name] = (param_type, ...)
|
|
279
|
-
|
|
283
|
+
|
|
280
284
|
# Create dynamic Pydantic model
|
|
281
285
|
if fields:
|
|
282
286
|
return create_model(f"{func.__name__}Args", **fields)
|
|
283
287
|
else:
|
|
284
288
|
# Return a simple BaseModel if no parameters
|
|
285
289
|
return create_model(f"{func.__name__}Args")
|
|
286
|
-
|
|
290
|
+
|
|
287
291
|
except ImportError:
|
|
288
292
|
# Fallback to simple dict-based schema if Pydantic not available
|
|
289
293
|
signature = inspect.signature(func)
|
|
290
294
|
type_hints = get_type_hints(func)
|
|
291
|
-
|
|
295
|
+
|
|
292
296
|
properties = {}
|
|
293
297
|
required = []
|
|
294
|
-
|
|
298
|
+
|
|
295
299
|
for param_name, param in signature.parameters.items():
|
|
296
|
-
if param_name ==
|
|
300
|
+
if param_name == "context":
|
|
297
301
|
continue
|
|
298
|
-
|
|
302
|
+
|
|
299
303
|
param_type = type_hints.get(param_name, str)
|
|
300
|
-
|
|
304
|
+
|
|
301
305
|
# Convert Python types to JSON schema types
|
|
302
306
|
if param_type == str:
|
|
303
307
|
properties[param_name] = {"type": "string"}
|
|
@@ -309,16 +313,12 @@ def _create_parameter_schema_from_signature(func):
|
|
|
309
313
|
properties[param_name] = {"type": "boolean"}
|
|
310
314
|
else:
|
|
311
315
|
properties[param_name] = {"type": "string"} # Default fallback
|
|
312
|
-
|
|
316
|
+
|
|
313
317
|
# Check if parameter is required
|
|
314
318
|
if param.default == inspect.Parameter.empty:
|
|
315
319
|
required.append(param_name)
|
|
316
|
-
|
|
317
|
-
return {
|
|
318
|
-
"type": "object",
|
|
319
|
-
"properties": properties,
|
|
320
|
-
"required": required
|
|
321
|
-
}
|
|
320
|
+
|
|
321
|
+
return {"type": "object", "properties": properties, "required": required}
|
|
322
322
|
|
|
323
323
|
|
|
324
324
|
def function_tool(
|
|
@@ -328,19 +328,19 @@ def function_tool(
|
|
|
328
328
|
description: Optional[str] = None,
|
|
329
329
|
metadata: Optional[Dict[str, Any]] = None,
|
|
330
330
|
source: Optional[ToolSource] = None,
|
|
331
|
-
timeout: Optional[float] = None
|
|
331
|
+
timeout: Optional[float] = None,
|
|
332
332
|
):
|
|
333
333
|
"""
|
|
334
334
|
Decorator to automatically create a tool from a function.
|
|
335
|
-
|
|
335
|
+
|
|
336
336
|
This decorator extracts type information from function annotations and
|
|
337
337
|
docstrings to automatically create a properly configured tool by adding
|
|
338
338
|
tool properties and methods directly to the function.
|
|
339
|
-
|
|
339
|
+
|
|
340
340
|
Can be used with or without parameters:
|
|
341
341
|
- @function_tool
|
|
342
342
|
- @function_tool(name="custom", description="Custom tool")
|
|
343
|
-
|
|
343
|
+
|
|
344
344
|
Args:
|
|
345
345
|
func_or_name: When used as @function_tool, this is the function being decorated.
|
|
346
346
|
When used as @function_tool(...), this should be None.
|
|
@@ -348,18 +348,18 @@ def function_tool(
|
|
|
348
348
|
description: Optional custom description (defaults to docstring)
|
|
349
349
|
metadata: Optional metadata for the tool
|
|
350
350
|
source: Optional source tracking for the tool
|
|
351
|
-
|
|
351
|
+
|
|
352
352
|
Returns:
|
|
353
353
|
A Tool implementation that can be used with agents.
|
|
354
|
-
|
|
354
|
+
|
|
355
355
|
Example:
|
|
356
356
|
```python
|
|
357
357
|
from jaf import function_tool
|
|
358
|
-
|
|
358
|
+
|
|
359
359
|
@function_tool
|
|
360
360
|
async def fetch_weather(location: str, context) -> str:
|
|
361
361
|
'''Fetch the weather for a given location.
|
|
362
|
-
|
|
362
|
+
|
|
363
363
|
Args:
|
|
364
364
|
location: The location to fetch the weather for.
|
|
365
365
|
'''
|
|
@@ -367,55 +367,53 @@ def function_tool(
|
|
|
367
367
|
return "sunny"
|
|
368
368
|
```
|
|
369
369
|
"""
|
|
370
|
+
|
|
370
371
|
def create_tool_from_func(func):
|
|
371
372
|
# Extract function information
|
|
372
373
|
func_name = name or func.__name__
|
|
373
374
|
func_description, param_descriptions = _extract_docstring_info(func)
|
|
374
375
|
if description:
|
|
375
376
|
func_description = description
|
|
376
|
-
|
|
377
|
+
|
|
377
378
|
# Create parameter schema
|
|
378
379
|
parameters = _create_parameter_schema_from_signature(func)
|
|
379
|
-
|
|
380
|
+
|
|
380
381
|
# Store the original function
|
|
381
382
|
original_func = func
|
|
382
|
-
|
|
383
|
+
|
|
383
384
|
# Create schema
|
|
384
385
|
tool_schema = ToolSchema(
|
|
385
|
-
name=func_name,
|
|
386
|
-
description=func_description,
|
|
387
|
-
parameters=parameters,
|
|
388
|
-
timeout=timeout
|
|
386
|
+
name=func_name, description=func_description, parameters=parameters, timeout=timeout
|
|
389
387
|
)
|
|
390
|
-
|
|
388
|
+
|
|
391
389
|
# Add tool properties and methods to the function
|
|
392
390
|
func.schema = tool_schema
|
|
393
391
|
func.metadata = metadata or {}
|
|
394
392
|
func.source = source or ToolSource.NATIVE
|
|
395
|
-
|
|
393
|
+
|
|
396
394
|
# Add execute method that calls the original function
|
|
397
395
|
async def execute(args: Any, context: Any) -> Union[str, ToolResult]:
|
|
398
396
|
"""Execute the tool with given arguments and context."""
|
|
399
397
|
# Check if args is a Pydantic model (from JAF engine) or individual parameters (manual call)
|
|
400
|
-
if hasattr(args,
|
|
398
|
+
if hasattr(args, "model_dump"): # Pydantic v2
|
|
401
399
|
# Unpack Pydantic model to individual parameters
|
|
402
400
|
kwargs = args.model_dump()
|
|
403
401
|
result = original_func(**kwargs, context=context)
|
|
404
|
-
elif hasattr(args,
|
|
402
|
+
elif hasattr(args, "dict"): # Pydantic v1
|
|
405
403
|
# Unpack Pydantic model to individual parameters
|
|
406
404
|
kwargs = args.dict()
|
|
407
405
|
result = original_func(**kwargs, context=context)
|
|
408
406
|
else:
|
|
409
407
|
# Assume it's already unpacked parameters (backward compatibility)
|
|
410
408
|
result = original_func(args, context)
|
|
411
|
-
|
|
409
|
+
|
|
412
410
|
# Handle both sync and async execute functions
|
|
413
|
-
if hasattr(result,
|
|
411
|
+
if hasattr(result, "__await__"):
|
|
414
412
|
return await result
|
|
415
413
|
return result
|
|
416
|
-
|
|
414
|
+
|
|
417
415
|
func.execute = execute
|
|
418
|
-
|
|
416
|
+
|
|
419
417
|
# Add __call__ method that provides helpful error message
|
|
420
418
|
def call_method(*args, **kwargs):
|
|
421
419
|
"""Provide helpful error for incorrect tool usage."""
|
|
@@ -424,23 +422,23 @@ def function_tool(
|
|
|
424
422
|
f"from JAF engine, or directly as 'await {func_name}(param1, param2, ..., context)' "
|
|
425
423
|
f"for manual execution. Direct tool object calls are not supported."
|
|
426
424
|
)
|
|
427
|
-
|
|
425
|
+
|
|
428
426
|
func.__call__ = call_method
|
|
429
|
-
|
|
427
|
+
|
|
430
428
|
return func
|
|
431
|
-
|
|
429
|
+
|
|
432
430
|
# If func_or_name is a callable, this means the decorator was used without parentheses: @function_tool
|
|
433
431
|
if callable(func_or_name):
|
|
434
432
|
return create_tool_from_func(func_or_name)
|
|
435
|
-
|
|
433
|
+
|
|
436
434
|
# Otherwise, this means the decorator was used with parentheses: @function_tool(...)
|
|
437
435
|
# In this case, func_or_name might be None or the name parameter
|
|
438
436
|
if func_or_name is not None and name is None:
|
|
439
437
|
# Handle the case where the first parameter was meant to be the name
|
|
440
438
|
name = func_or_name
|
|
441
|
-
|
|
439
|
+
|
|
442
440
|
# Return the decorator function
|
|
443
441
|
def decorator(func):
|
|
444
442
|
return create_tool_from_func(func)
|
|
445
|
-
|
|
443
|
+
|
|
446
444
|
return decorator
|