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/agent_tool.py
CHANGED
|
@@ -33,11 +33,13 @@ from .types import (
|
|
|
33
33
|
generate_trace_id,
|
|
34
34
|
)
|
|
35
35
|
|
|
36
|
-
Ctx = TypeVar(
|
|
37
|
-
Out = TypeVar(
|
|
36
|
+
Ctx = TypeVar("Ctx")
|
|
37
|
+
Out = TypeVar("Out")
|
|
38
38
|
|
|
39
39
|
# Context variable to store the current RunConfig for agent tools
|
|
40
|
-
_current_run_config: contextvars.ContextVar[Optional[RunConfig]] = contextvars.ContextVar(
|
|
40
|
+
_current_run_config: contextvars.ContextVar[Optional[RunConfig]] = contextvars.ContextVar(
|
|
41
|
+
"current_run_config", default=None
|
|
42
|
+
)
|
|
41
43
|
|
|
42
44
|
|
|
43
45
|
def set_current_run_config(config: RunConfig) -> None:
|
|
@@ -52,9 +54,11 @@ def get_current_run_config() -> Optional[RunConfig]:
|
|
|
52
54
|
|
|
53
55
|
class AgentToolInput(BaseModel if BaseModel else object):
|
|
54
56
|
"""Input parameters for agent tools."""
|
|
57
|
+
|
|
55
58
|
input: str
|
|
56
|
-
|
|
59
|
+
|
|
57
60
|
if not BaseModel:
|
|
61
|
+
|
|
58
62
|
def __init__(self, input: str):
|
|
59
63
|
self.input = input
|
|
60
64
|
|
|
@@ -64,15 +68,21 @@ def create_agent_tool(
|
|
|
64
68
|
tool_name: Optional[str] = None,
|
|
65
69
|
tool_description: Optional[str] = None,
|
|
66
70
|
max_turns: Optional[int] = None,
|
|
67
|
-
custom_output_extractor: Optional[
|
|
68
|
-
|
|
71
|
+
custom_output_extractor: Optional[
|
|
72
|
+
Callable[[RunResult[Out]], Union[str, Awaitable[str]]]
|
|
73
|
+
] = None,
|
|
74
|
+
is_enabled: Union[
|
|
75
|
+
bool,
|
|
76
|
+
Callable[[Any, Agent[Ctx, Out]], bool],
|
|
77
|
+
Callable[[Any, Agent[Ctx, Out]], Awaitable[bool]],
|
|
78
|
+
] = True,
|
|
69
79
|
metadata: Optional[Dict[str, Any]] = None,
|
|
70
80
|
timeout: Optional[float] = None,
|
|
71
|
-
preserve_session: bool = False
|
|
81
|
+
preserve_session: bool = False,
|
|
72
82
|
) -> Tool[AgentToolInput, Ctx]:
|
|
73
83
|
"""
|
|
74
84
|
Create a tool from an agent.
|
|
75
|
-
|
|
85
|
+
|
|
76
86
|
Args:
|
|
77
87
|
agent: The agent to convert into a tool
|
|
78
88
|
tool_name: Optional custom name for the tool
|
|
@@ -82,15 +92,17 @@ def create_agent_tool(
|
|
|
82
92
|
is_enabled: Whether the tool is enabled (bool, sync function, or async function)
|
|
83
93
|
metadata: Optional metadata for the tool
|
|
84
94
|
timeout: Optional timeout for tool execution
|
|
85
|
-
|
|
95
|
+
|
|
86
96
|
Returns:
|
|
87
97
|
A Tool that wraps the agent execution
|
|
88
98
|
"""
|
|
89
|
-
|
|
99
|
+
|
|
90
100
|
# Default names and descriptions
|
|
91
101
|
final_tool_name = tool_name or f"run_{agent.name.lower().replace(' ', '_')}"
|
|
92
|
-
final_tool_description =
|
|
93
|
-
|
|
102
|
+
final_tool_description = (
|
|
103
|
+
tool_description or f"Execute the {agent.name} agent with the given input"
|
|
104
|
+
)
|
|
105
|
+
|
|
94
106
|
# Create the tool schema
|
|
95
107
|
if BaseModel and create_model:
|
|
96
108
|
# Use Pydantic if available
|
|
@@ -102,67 +114,68 @@ def create_agent_tool(
|
|
|
102
114
|
"properties": {
|
|
103
115
|
"input": {"type": "string", "description": "The input message to send to the agent"}
|
|
104
116
|
},
|
|
105
|
-
"required": ["input"]
|
|
117
|
+
"required": ["input"],
|
|
106
118
|
}
|
|
107
|
-
|
|
119
|
+
|
|
108
120
|
tool_schema = ToolSchema(
|
|
109
121
|
name=final_tool_name,
|
|
110
122
|
description=final_tool_description,
|
|
111
123
|
parameters=parameters_model,
|
|
112
|
-
timeout=timeout
|
|
124
|
+
timeout=timeout,
|
|
113
125
|
)
|
|
114
|
-
|
|
126
|
+
|
|
115
127
|
async def _check_if_enabled(context: Ctx) -> bool:
|
|
116
128
|
"""Check if the tool is enabled based on the is_enabled parameter."""
|
|
117
129
|
if isinstance(is_enabled, bool):
|
|
118
130
|
return is_enabled
|
|
119
131
|
elif callable(is_enabled):
|
|
120
132
|
result = is_enabled(context, agent)
|
|
121
|
-
if hasattr(result,
|
|
133
|
+
if hasattr(result, "__await__"):
|
|
122
134
|
return await result
|
|
123
135
|
return result
|
|
124
136
|
return True
|
|
125
|
-
|
|
137
|
+
|
|
126
138
|
async def _execute_agent_tool(args: AgentToolInput, context: Ctx) -> str:
|
|
127
139
|
"""Execute the agent and return the result."""
|
|
128
140
|
# Check if tool is enabled
|
|
129
141
|
if not await _check_if_enabled(context):
|
|
130
|
-
return json.dumps(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
142
|
+
return json.dumps(
|
|
143
|
+
{
|
|
144
|
+
"error": "tool_disabled",
|
|
145
|
+
"message": f"Tool {final_tool_name} is currently disabled",
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
|
|
135
149
|
# Extract input from args
|
|
136
|
-
if hasattr(args,
|
|
150
|
+
if hasattr(args, "input"):
|
|
137
151
|
user_input = args.input
|
|
138
152
|
elif isinstance(args, dict):
|
|
139
|
-
user_input = args.get(
|
|
153
|
+
user_input = args.get("input", "")
|
|
140
154
|
else:
|
|
141
155
|
user_input = str(args)
|
|
142
|
-
|
|
156
|
+
|
|
143
157
|
# Create initial state for the agent
|
|
144
|
-
initial_messages = [Message(
|
|
145
|
-
|
|
146
|
-
content=user_input
|
|
147
|
-
)]
|
|
148
|
-
|
|
158
|
+
initial_messages = [Message(role=ContentRole.USER, content=user_input)]
|
|
159
|
+
|
|
149
160
|
initial_state = RunState(
|
|
150
161
|
run_id=generate_run_id(),
|
|
151
162
|
trace_id=generate_trace_id(),
|
|
152
163
|
messages=initial_messages,
|
|
153
164
|
current_agent_name=agent.name,
|
|
154
165
|
context=context,
|
|
155
|
-
turn_count=0
|
|
166
|
+
turn_count=0,
|
|
156
167
|
)
|
|
157
|
-
|
|
168
|
+
|
|
158
169
|
# Get the current RunConfig from context variable
|
|
159
170
|
parent_config = _current_run_config.get()
|
|
160
171
|
if parent_config is None:
|
|
161
172
|
# If no parent config available, we can't execute the agent
|
|
162
|
-
return json.dumps(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
173
|
+
return json.dumps(
|
|
174
|
+
{
|
|
175
|
+
"error": "no_parent_config",
|
|
176
|
+
"message": f"Agent tool {final_tool_name} requires a parent RunConfig to execute. Please ensure the agent tool is called from within a JAF run context.",
|
|
177
|
+
}
|
|
178
|
+
)
|
|
166
179
|
|
|
167
180
|
# Create a sub-config that inherits from parent but uses this agent
|
|
168
181
|
# Session inheritance is configurable via preserve_session.
|
|
@@ -179,13 +192,14 @@ def create_agent_tool(
|
|
|
179
192
|
memory=parent_config.memory if preserve_session else None,
|
|
180
193
|
conversation_id=parent_config.conversation_id if preserve_session else None,
|
|
181
194
|
default_tool_timeout=parent_config.default_tool_timeout,
|
|
182
|
-
prefer_streaming=parent_config.prefer_streaming
|
|
195
|
+
prefer_streaming=parent_config.prefer_streaming,
|
|
183
196
|
)
|
|
184
197
|
|
|
185
198
|
token = _current_run_config.set(sub_config)
|
|
186
199
|
try:
|
|
187
200
|
# Import here to avoid circular imports
|
|
188
201
|
from . import engine
|
|
202
|
+
|
|
189
203
|
# Execute the agent
|
|
190
204
|
result = await engine.run(initial_state, sub_config)
|
|
191
205
|
finally:
|
|
@@ -198,14 +212,16 @@ def create_agent_tool(
|
|
|
198
212
|
if inspect.isawaitable(output):
|
|
199
213
|
output = await output
|
|
200
214
|
return str(output)
|
|
201
|
-
if result.outcome.status ==
|
|
202
|
-
if hasattr(result.outcome,
|
|
215
|
+
if result.outcome.status == "completed":
|
|
216
|
+
if hasattr(result.outcome, "output") and result.outcome.output is not None:
|
|
203
217
|
return str(result.outcome.output)
|
|
204
218
|
else:
|
|
205
219
|
# Fall back to the last assistant message
|
|
206
220
|
from .types import get_text_content
|
|
221
|
+
|
|
207
222
|
assistant_messages = [
|
|
208
|
-
msg
|
|
223
|
+
msg
|
|
224
|
+
for msg in result.final_state.messages
|
|
209
225
|
if msg.role == ContentRole.ASSISTANT and get_text_content(msg.content)
|
|
210
226
|
]
|
|
211
227
|
if assistant_messages:
|
|
@@ -213,43 +229,48 @@ def create_agent_tool(
|
|
|
213
229
|
return "Agent completed successfully but produced no output"
|
|
214
230
|
else:
|
|
215
231
|
# Error case
|
|
216
|
-
error_detail = getattr(result.outcome.error,
|
|
217
|
-
return json.dumps(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
232
|
+
error_detail = getattr(result.outcome.error, "detail", str(result.outcome.error))
|
|
233
|
+
return json.dumps(
|
|
234
|
+
{
|
|
235
|
+
"error": "agent_execution_failed",
|
|
236
|
+
"message": f"Agent {agent.name} failed: {error_detail}",
|
|
237
|
+
}
|
|
238
|
+
)
|
|
221
239
|
except Exception as e:
|
|
222
|
-
return json.dumps(
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
240
|
+
return json.dumps(
|
|
241
|
+
{
|
|
242
|
+
"error": "agent_tool_error",
|
|
243
|
+
"message": f"Error executing agent {agent.name}: {str(e)}",
|
|
244
|
+
}
|
|
245
|
+
)
|
|
246
|
+
|
|
227
247
|
# Create the tool wrapper
|
|
228
248
|
class AgentTool:
|
|
229
249
|
def __init__(self):
|
|
230
250
|
self.schema = tool_schema
|
|
231
251
|
self.metadata = metadata or {"source": "agent", "agent_name": agent.name}
|
|
232
252
|
self.source = ToolSource.NATIVE
|
|
233
|
-
|
|
253
|
+
|
|
234
254
|
async def execute(self, args: AgentToolInput, context: Ctx) -> str:
|
|
235
255
|
"""Execute the agent tool."""
|
|
236
256
|
return await _execute_agent_tool(args, context)
|
|
237
|
-
|
|
257
|
+
|
|
238
258
|
return AgentTool()
|
|
239
259
|
|
|
240
260
|
|
|
241
261
|
def create_default_output_extractor(extract_json: bool = False) -> Callable[[RunResult], str]:
|
|
242
262
|
"""
|
|
243
263
|
Create a default output extractor function.
|
|
244
|
-
|
|
264
|
+
|
|
245
265
|
Args:
|
|
246
266
|
extract_json: If True, attempts to extract JSON from the output
|
|
247
|
-
|
|
267
|
+
|
|
248
268
|
Returns:
|
|
249
269
|
An output extractor function
|
|
250
270
|
"""
|
|
271
|
+
|
|
251
272
|
def extractor(run_result: RunResult) -> str:
|
|
252
|
-
if run_result.outcome.status ==
|
|
273
|
+
if run_result.outcome.status == "completed":
|
|
253
274
|
output = run_result.outcome.output
|
|
254
275
|
if extract_json and isinstance(output, str):
|
|
255
276
|
try:
|
|
@@ -262,57 +283,57 @@ def create_default_output_extractor(extract_json: bool = False) -> Callable[[Run
|
|
|
262
283
|
return str(output) if output is not None else ""
|
|
263
284
|
else:
|
|
264
285
|
# Return error information
|
|
265
|
-
error_detail = getattr(
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
286
|
+
error_detail = getattr(
|
|
287
|
+
run_result.outcome.error, "detail", str(run_result.outcome.error)
|
|
288
|
+
)
|
|
289
|
+
return json.dumps({"error": True, "message": error_detail})
|
|
290
|
+
|
|
271
291
|
return extractor
|
|
272
292
|
|
|
273
293
|
|
|
274
294
|
def create_json_output_extractor() -> Callable[[RunResult], str]:
|
|
275
295
|
"""
|
|
276
296
|
Create an output extractor that specifically looks for JSON in the agent's output.
|
|
277
|
-
|
|
297
|
+
|
|
278
298
|
Returns:
|
|
279
299
|
An output extractor that finds and returns JSON content
|
|
280
300
|
"""
|
|
301
|
+
|
|
281
302
|
def json_extractor(run_result: RunResult) -> str:
|
|
282
303
|
# Scan the agent's outputs in reverse order until we find a JSON-like message
|
|
283
304
|
for message in reversed(run_result.final_state.messages):
|
|
284
305
|
if message.role == ContentRole.ASSISTANT and get_text_content(message.content):
|
|
285
306
|
content = get_text_content(message.content).strip()
|
|
286
|
-
if content.startswith(
|
|
307
|
+
if content.startswith("{") or content.startswith("["):
|
|
287
308
|
try:
|
|
288
309
|
# Validate it's proper JSON
|
|
289
310
|
json.loads(content)
|
|
290
311
|
return content
|
|
291
312
|
except (json.JSONDecodeError, TypeError):
|
|
292
313
|
continue
|
|
293
|
-
|
|
314
|
+
|
|
294
315
|
# Fallback to empty JSON object if nothing was found
|
|
295
316
|
return "{}"
|
|
296
|
-
|
|
317
|
+
|
|
297
318
|
return json_extractor
|
|
298
319
|
|
|
299
320
|
|
|
300
321
|
# Convenience function for conditional tool enabling
|
|
301
322
|
def create_conditional_enabler(
|
|
302
|
-
condition_key: str,
|
|
303
|
-
expected_value: Any = True
|
|
323
|
+
condition_key: str, expected_value: Any = True
|
|
304
324
|
) -> Callable[[Any, Agent], bool]:
|
|
305
325
|
"""
|
|
306
326
|
Create a conditional enabler function based on context attributes.
|
|
307
|
-
|
|
327
|
+
|
|
308
328
|
Args:
|
|
309
329
|
condition_key: The key to check in the context
|
|
310
330
|
expected_value: The expected value for the condition
|
|
311
|
-
|
|
331
|
+
|
|
312
332
|
Returns:
|
|
313
333
|
A function that checks if the tool should be enabled
|
|
314
334
|
"""
|
|
335
|
+
|
|
315
336
|
def enabler(context: Any, agent: Agent) -> bool:
|
|
316
337
|
return getattr(context, condition_key, None) == expected_value
|
|
317
|
-
|
|
318
|
-
return enabler
|
|
338
|
+
|
|
339
|
+
return enabler
|