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
|
@@ -36,7 +36,7 @@ class MockModelProvider:
|
|
|
36
36
|
return {
|
|
37
37
|
"message": {
|
|
38
38
|
"content": f"[{agent.name}] I received: '{last_message}'",
|
|
39
|
-
"tool_calls": None
|
|
39
|
+
"tool_calls": None,
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
@@ -55,7 +55,7 @@ async def echo_tool(args: EchoArgs, context) -> Dict[str, Any]:
|
|
|
55
55
|
return {
|
|
56
56
|
"result": f"Echo: {args.message}",
|
|
57
57
|
"original": args.message,
|
|
58
|
-
"length": len(args.message)
|
|
58
|
+
"length": len(args.message),
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
|
|
@@ -66,11 +66,7 @@ async def count_tool(args: CountArgs, context) -> Dict[str, Any]:
|
|
|
66
66
|
|
|
67
67
|
return {
|
|
68
68
|
"result": f"Text analysis: {char_count} characters, {word_count} words",
|
|
69
|
-
"analysis": {
|
|
70
|
-
"characters": char_count,
|
|
71
|
-
"words": word_count,
|
|
72
|
-
"text": args.text
|
|
73
|
-
}
|
|
69
|
+
"analysis": {"characters": char_count, "words": word_count, "text": args.text},
|
|
74
70
|
}
|
|
75
71
|
|
|
76
72
|
|
|
@@ -79,17 +75,14 @@ def create_test_agents():
|
|
|
79
75
|
|
|
80
76
|
# Echo Agent
|
|
81
77
|
echo_tool_obj = create_a2a_tool(
|
|
82
|
-
"echo",
|
|
83
|
-
"Echo back a message",
|
|
84
|
-
EchoArgs.model_json_schema(),
|
|
85
|
-
echo_tool
|
|
78
|
+
"echo", "Echo back a message", EchoArgs.model_json_schema(), echo_tool
|
|
86
79
|
)
|
|
87
80
|
|
|
88
81
|
echo_agent = create_a2a_agent(
|
|
89
82
|
"EchoBot",
|
|
90
83
|
"An agent that echoes messages back",
|
|
91
84
|
"You are an echo bot. Use the echo tool to repeat messages.",
|
|
92
|
-
[echo_tool_obj]
|
|
85
|
+
[echo_tool_obj],
|
|
93
86
|
)
|
|
94
87
|
|
|
95
88
|
# Counter Agent
|
|
@@ -97,14 +90,14 @@ def create_test_agents():
|
|
|
97
90
|
"count_chars",
|
|
98
91
|
"Count characters and words in text",
|
|
99
92
|
CountArgs.model_json_schema(),
|
|
100
|
-
count_tool
|
|
93
|
+
count_tool,
|
|
101
94
|
)
|
|
102
95
|
|
|
103
96
|
counter_agent = create_a2a_agent(
|
|
104
97
|
"CounterBot",
|
|
105
98
|
"An agent that counts characters and words",
|
|
106
99
|
"You are a text analysis bot. Use the counting tool to analyze text.",
|
|
107
|
-
[count_tool_obj]
|
|
100
|
+
[count_tool_obj],
|
|
108
101
|
)
|
|
109
102
|
|
|
110
103
|
# Simple Chat Agent (no tools)
|
|
@@ -112,14 +105,10 @@ def create_test_agents():
|
|
|
112
105
|
"ChatBot",
|
|
113
106
|
"A simple conversational agent",
|
|
114
107
|
"You are a friendly chat bot. Be helpful and conversational.",
|
|
115
|
-
[]
|
|
108
|
+
[],
|
|
116
109
|
)
|
|
117
110
|
|
|
118
|
-
return {
|
|
119
|
-
"EchoBot": echo_agent,
|
|
120
|
-
"CounterBot": counter_agent,
|
|
121
|
-
"ChatBot": chat_agent
|
|
122
|
-
}
|
|
111
|
+
return {"EchoBot": echo_agent, "CounterBot": counter_agent, "ChatBot": chat_agent}
|
|
123
112
|
|
|
124
113
|
|
|
125
114
|
async def start_test_server():
|
|
@@ -134,7 +123,7 @@ async def start_test_server():
|
|
|
134
123
|
name="Test A2A Server",
|
|
135
124
|
description="Integration test server with sample agents",
|
|
136
125
|
host="localhost",
|
|
137
|
-
port=3001 # Use different port to avoid conflicts
|
|
126
|
+
port=3001, # Use different port to avoid conflicts
|
|
138
127
|
)
|
|
139
128
|
|
|
140
129
|
server_config["model_provider"] = MockModelProvider()
|
|
@@ -163,18 +152,18 @@ async def test_client_interactions(base_url: str):
|
|
|
163
152
|
{
|
|
164
153
|
"agent": "EchoBot",
|
|
165
154
|
"message": "Hello, echo bot!",
|
|
166
|
-
"description": "Testing echo functionality"
|
|
155
|
+
"description": "Testing echo functionality",
|
|
167
156
|
},
|
|
168
157
|
{
|
|
169
158
|
"agent": "CounterBot",
|
|
170
159
|
"message": "Count the characters in this sentence please",
|
|
171
|
-
"description": "Testing character counting"
|
|
160
|
+
"description": "Testing character counting",
|
|
172
161
|
},
|
|
173
162
|
{
|
|
174
163
|
"agent": "ChatBot",
|
|
175
164
|
"message": "Hi there, how are you doing today?",
|
|
176
|
-
"description": "Testing general conversation"
|
|
177
|
-
}
|
|
165
|
+
"description": "Testing general conversation",
|
|
166
|
+
},
|
|
178
167
|
]
|
|
179
168
|
|
|
180
169
|
results = []
|
|
@@ -185,29 +174,29 @@ async def test_client_interactions(base_url: str):
|
|
|
185
174
|
print(f" Message: {test_case['message']}")
|
|
186
175
|
|
|
187
176
|
try:
|
|
188
|
-
response = await send_message_to_agent(
|
|
189
|
-
client,
|
|
190
|
-
test_case['agent'],
|
|
191
|
-
test_case['message']
|
|
192
|
-
)
|
|
177
|
+
response = await send_message_to_agent(client, test_case["agent"], test_case["message"])
|
|
193
178
|
|
|
194
179
|
print(f"✅ Response: {response}")
|
|
195
180
|
|
|
196
|
-
results.append(
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
181
|
+
results.append(
|
|
182
|
+
{
|
|
183
|
+
"agent": test_case["agent"],
|
|
184
|
+
"message": test_case["message"],
|
|
185
|
+
"response": response,
|
|
186
|
+
"success": True,
|
|
187
|
+
}
|
|
188
|
+
)
|
|
202
189
|
|
|
203
190
|
except Exception as error:
|
|
204
191
|
print(f"❌ Error: {error}")
|
|
205
|
-
results.append(
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
192
|
+
results.append(
|
|
193
|
+
{
|
|
194
|
+
"agent": test_case["agent"],
|
|
195
|
+
"message": test_case["message"],
|
|
196
|
+
"error": str(error),
|
|
197
|
+
"success": False,
|
|
198
|
+
}
|
|
199
|
+
)
|
|
211
200
|
|
|
212
201
|
return results
|
|
213
202
|
|
|
@@ -224,7 +213,7 @@ async def test_agent_discovery(base_url: str):
|
|
|
224
213
|
print(f"✅ Discovered server: {agent_card['name']}")
|
|
225
214
|
print(f"📄 Description: {agent_card['description']}")
|
|
226
215
|
|
|
227
|
-
skills = agent_card.get(
|
|
216
|
+
skills = agent_card.get("skills", [])
|
|
228
217
|
print(f"🛠️ Skills found: {len(skills)}")
|
|
229
218
|
|
|
230
219
|
for skill in skills:
|
|
@@ -252,7 +241,7 @@ async def test_health_and_capabilities(base_url: str):
|
|
|
252
241
|
|
|
253
242
|
# Capabilities
|
|
254
243
|
capabilities = await get_a2a_capabilities(client)
|
|
255
|
-
methods = capabilities.get(
|
|
244
|
+
methods = capabilities.get("supportedMethods", [])
|
|
256
245
|
print(f"⚡ Supported methods: {', '.join(methods)}")
|
|
257
246
|
|
|
258
247
|
return True
|
|
@@ -294,7 +283,7 @@ async def main():
|
|
|
294
283
|
print("\n📊 Integration Test Summary")
|
|
295
284
|
print("=" * 30)
|
|
296
285
|
|
|
297
|
-
successful = sum(1 for r in results if r[
|
|
286
|
+
successful = sum(1 for r in results if r["success"])
|
|
298
287
|
total = len(results)
|
|
299
288
|
|
|
300
289
|
print(f"✅ Successful interactions: {successful}/{total}")
|
|
@@ -305,8 +294,10 @@ async def main():
|
|
|
305
294
|
print("⚠️ Some tests failed. Check the errors above.")
|
|
306
295
|
|
|
307
296
|
for result in results:
|
|
308
|
-
status = "✅" if result[
|
|
309
|
-
print(
|
|
297
|
+
status = "✅" if result["success"] else "❌"
|
|
298
|
+
print(
|
|
299
|
+
f" {status} {result['agent']}: {result.get('response', result.get('error', 'Unknown'))[:60]}..."
|
|
300
|
+
)
|
|
310
301
|
|
|
311
302
|
print("\n🔧 Integration Features Tested:")
|
|
312
303
|
print(" • Agent discovery via Agent Cards")
|
|
@@ -9,7 +9,7 @@ Usage:
|
|
|
9
9
|
|
|
10
10
|
The server will start on http://localhost:3000 with the following endpoints:
|
|
11
11
|
- /.well-known/agent-card - Agent discovery
|
|
12
|
-
- /a2a - Main A2A JSON-RPC endpoint
|
|
12
|
+
- /a2a - Main A2A JSON-RPC endpoint
|
|
13
13
|
- /a2a/agents/{name} - Agent-specific endpoints
|
|
14
14
|
- /a2a/health - Health check
|
|
15
15
|
- /a2a/capabilities - Server capabilities
|
|
@@ -39,21 +39,21 @@ class MockModelProvider:
|
|
|
39
39
|
return {
|
|
40
40
|
"message": {
|
|
41
41
|
"content": f"I can help with math! I see you said: '{last_message}'",
|
|
42
|
-
"tool_calls": None
|
|
42
|
+
"tool_calls": None,
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
elif "weather" in agent_name:
|
|
46
46
|
return {
|
|
47
47
|
"message": {
|
|
48
48
|
"content": f"I can check weather! You asked: '{last_message}'",
|
|
49
|
-
"tool_calls": None
|
|
49
|
+
"tool_calls": None,
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
else:
|
|
53
53
|
return {
|
|
54
54
|
"message": {
|
|
55
55
|
"content": f"Hello! I'm {agent.name}. You said: '{last_message}'",
|
|
56
|
-
"tool_calls": None
|
|
56
|
+
"tool_calls": None,
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
@@ -61,17 +61,20 @@ class MockModelProvider:
|
|
|
61
61
|
# Tool argument models
|
|
62
62
|
class CalculateArgs(BaseModel):
|
|
63
63
|
"""Arguments for calculator tool"""
|
|
64
|
+
|
|
64
65
|
expression: str = Field(description="Mathematical expression to evaluate")
|
|
65
66
|
|
|
66
67
|
|
|
67
68
|
class WeatherArgs(BaseModel):
|
|
68
69
|
"""Arguments for weather tool"""
|
|
70
|
+
|
|
69
71
|
location: str = Field(description="City or location name")
|
|
70
72
|
units: str = Field(default="celsius", description="Temperature units (celsius/fahrenheit)")
|
|
71
73
|
|
|
72
74
|
|
|
73
75
|
class TranslateArgs(BaseModel):
|
|
74
76
|
"""Arguments for translation tool"""
|
|
77
|
+
|
|
75
78
|
text: str = Field(description="Text to translate")
|
|
76
79
|
target_language: str = Field(description="Target language code (e.g., 'es', 'fr', 'de')")
|
|
77
80
|
|
|
@@ -95,10 +98,10 @@ async def calculator_tool(args: CalculateArgs, context) -> Dict[str, Any]:
|
|
|
95
98
|
}
|
|
96
99
|
|
|
97
100
|
allowed_functions = {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
"abs": abs,
|
|
102
|
+
"round": round,
|
|
103
|
+
"min": min,
|
|
104
|
+
"max": max,
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
def safe_eval_node(node):
|
|
@@ -107,8 +110,9 @@ async def calculator_tool(args: CalculateArgs, context) -> Dict[str, Any]:
|
|
|
107
110
|
return node.value
|
|
108
111
|
elif isinstance(node, ast.Name):
|
|
109
112
|
# Only allow specific constants
|
|
110
|
-
if node.id in [
|
|
113
|
+
if node.id in ["pi", "e"]:
|
|
111
114
|
import math
|
|
115
|
+
|
|
112
116
|
return getattr(math, node.id)
|
|
113
117
|
else:
|
|
114
118
|
raise ValueError(f"Name '{node.id}' is not allowed")
|
|
@@ -139,9 +143,24 @@ async def calculator_tool(args: CalculateArgs, context) -> Dict[str, Any]:
|
|
|
139
143
|
|
|
140
144
|
# Additional security checks
|
|
141
145
|
dangerous_patterns = [
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
146
|
+
"import",
|
|
147
|
+
"exec",
|
|
148
|
+
"eval",
|
|
149
|
+
"__",
|
|
150
|
+
"open",
|
|
151
|
+
"file",
|
|
152
|
+
"input",
|
|
153
|
+
"raw_input",
|
|
154
|
+
"compile",
|
|
155
|
+
"globals",
|
|
156
|
+
"locals",
|
|
157
|
+
"vars",
|
|
158
|
+
"dir",
|
|
159
|
+
"getattr",
|
|
160
|
+
"setattr",
|
|
161
|
+
"hasattr",
|
|
162
|
+
"delattr",
|
|
163
|
+
"callable",
|
|
145
164
|
]
|
|
146
165
|
|
|
147
166
|
expression_lower = expression.lower()
|
|
@@ -149,51 +168,33 @@ async def calculator_tool(args: CalculateArgs, context) -> Dict[str, Any]:
|
|
|
149
168
|
if pattern in expression_lower:
|
|
150
169
|
return {
|
|
151
170
|
"error": f"Security violation: '{pattern}' is not allowed in expressions",
|
|
152
|
-
"result": None
|
|
171
|
+
"result": None,
|
|
153
172
|
}
|
|
154
173
|
|
|
155
174
|
# Parse expression into AST
|
|
156
175
|
try:
|
|
157
|
-
parsed = ast.parse(expression, mode=
|
|
176
|
+
parsed = ast.parse(expression, mode="eval")
|
|
158
177
|
except SyntaxError as e:
|
|
159
|
-
return {
|
|
160
|
-
"error": f"Invalid mathematical expression: {e!s}",
|
|
161
|
-
"result": None
|
|
162
|
-
}
|
|
178
|
+
return {"error": f"Invalid mathematical expression: {e!s}", "result": None}
|
|
163
179
|
|
|
164
180
|
# Evaluate safely
|
|
165
181
|
result = safe_eval_node(parsed.body)
|
|
166
182
|
|
|
167
183
|
# Handle division by zero and other math errors
|
|
168
184
|
if not isinstance(result, (int, float, complex)):
|
|
169
|
-
return {
|
|
170
|
-
"error": "Expression must evaluate to a number",
|
|
171
|
-
"result": None
|
|
172
|
-
}
|
|
185
|
+
return {"error": "Expression must evaluate to a number", "result": None}
|
|
173
186
|
|
|
174
187
|
return {
|
|
175
188
|
"result": f"The result of {expression} is {result}",
|
|
176
|
-
"calculation": {
|
|
177
|
-
"expression": expression,
|
|
178
|
-
"result": result
|
|
179
|
-
}
|
|
189
|
+
"calculation": {"expression": expression, "result": result},
|
|
180
190
|
}
|
|
181
191
|
|
|
182
192
|
except ZeroDivisionError:
|
|
183
|
-
return {
|
|
184
|
-
"error": "Division by zero is not allowed",
|
|
185
|
-
"result": None
|
|
186
|
-
}
|
|
193
|
+
return {"error": "Division by zero is not allowed", "result": None}
|
|
187
194
|
except ValueError as e:
|
|
188
|
-
return {
|
|
189
|
-
"error": f"Invalid expression: {e!s}",
|
|
190
|
-
"result": None
|
|
191
|
-
}
|
|
195
|
+
return {"error": f"Invalid expression: {e!s}", "result": None}
|
|
192
196
|
except Exception as e:
|
|
193
|
-
return {
|
|
194
|
-
"error": f"Calculation error: {e!s}",
|
|
195
|
-
"result": None
|
|
196
|
-
}
|
|
197
|
+
return {"error": f"Calculation error: {e!s}", "result": None}
|
|
197
198
|
|
|
198
199
|
|
|
199
200
|
async def weather_tool(args: WeatherArgs, context) -> Dict[str, Any]:
|
|
@@ -205,14 +206,14 @@ async def weather_tool(args: WeatherArgs, context) -> Dict[str, Any]:
|
|
|
205
206
|
"units": args.units,
|
|
206
207
|
"condition": "Partly cloudy",
|
|
207
208
|
"humidity": 65,
|
|
208
|
-
"wind_speed": 15
|
|
209
|
+
"wind_speed": 15,
|
|
209
210
|
}
|
|
210
211
|
|
|
211
212
|
temp_unit = "°C" if args.units == "celsius" else "°F"
|
|
212
213
|
|
|
213
214
|
return {
|
|
214
215
|
"result": f"Weather in {args.location}: {weather_data['temperature']}{temp_unit}, {weather_data['condition']}",
|
|
215
|
-
"weather_data": weather_data
|
|
216
|
+
"weather_data": weather_data,
|
|
216
217
|
}
|
|
217
218
|
|
|
218
219
|
|
|
@@ -224,18 +225,20 @@ async def translate_tool(args: TranslateArgs, context) -> Dict[str, Any]:
|
|
|
224
225
|
"fr": f"[French] {args.text}",
|
|
225
226
|
"de": f"[German] {args.text}",
|
|
226
227
|
"it": f"[Italian] {args.text}",
|
|
227
|
-
"pt": f"[Portuguese] {args.text}"
|
|
228
|
+
"pt": f"[Portuguese] {args.text}",
|
|
228
229
|
}
|
|
229
230
|
|
|
230
|
-
translated = translations.get(
|
|
231
|
+
translated = translations.get(
|
|
232
|
+
args.target_language, f"[{args.target_language.upper()}] {args.text}"
|
|
233
|
+
)
|
|
231
234
|
|
|
232
235
|
return {
|
|
233
236
|
"result": f"Translation to {args.target_language}: {translated}",
|
|
234
237
|
"translation": {
|
|
235
238
|
"original": args.text,
|
|
236
239
|
"translated": translated,
|
|
237
|
-
"target_language": args.target_language
|
|
238
|
-
}
|
|
240
|
+
"target_language": args.target_language,
|
|
241
|
+
},
|
|
239
242
|
}
|
|
240
243
|
|
|
241
244
|
|
|
@@ -247,14 +250,14 @@ def create_example_agents():
|
|
|
247
250
|
"calculator",
|
|
248
251
|
"Perform mathematical calculations",
|
|
249
252
|
CalculateArgs.model_json_schema(),
|
|
250
|
-
calculator_tool
|
|
253
|
+
calculator_tool,
|
|
251
254
|
)
|
|
252
255
|
|
|
253
256
|
math_agent = create_a2a_agent(
|
|
254
257
|
"MathTutor",
|
|
255
258
|
"A mathematical assistant that can solve equations and calculations",
|
|
256
259
|
"You are a helpful math tutor. Use the calculator tool for computations.",
|
|
257
|
-
[calc_tool]
|
|
260
|
+
[calc_tool],
|
|
258
261
|
)
|
|
259
262
|
|
|
260
263
|
# Weather Agent with weather tool
|
|
@@ -262,14 +265,14 @@ def create_example_agents():
|
|
|
262
265
|
"get_weather",
|
|
263
266
|
"Get current weather information for a location",
|
|
264
267
|
WeatherArgs.model_json_schema(),
|
|
265
|
-
weather_tool
|
|
268
|
+
weather_tool,
|
|
266
269
|
)
|
|
267
270
|
|
|
268
271
|
weather_agent = create_a2a_agent(
|
|
269
272
|
"WeatherBot",
|
|
270
273
|
"A weather assistant that provides current weather information",
|
|
271
274
|
"You are a weather assistant. Use the weather tool to get current conditions.",
|
|
272
|
-
[weather_tool_obj]
|
|
275
|
+
[weather_tool_obj],
|
|
273
276
|
)
|
|
274
277
|
|
|
275
278
|
# Translation Agent with translation tool
|
|
@@ -277,14 +280,14 @@ def create_example_agents():
|
|
|
277
280
|
"translate_text",
|
|
278
281
|
"Translate text between languages",
|
|
279
282
|
TranslateArgs.model_json_schema(),
|
|
280
|
-
translate_tool
|
|
283
|
+
translate_tool,
|
|
281
284
|
)
|
|
282
285
|
|
|
283
286
|
translation_agent = create_a2a_agent(
|
|
284
287
|
"Translator",
|
|
285
288
|
"A multilingual assistant that can translate text between languages",
|
|
286
289
|
"You are a translation assistant. Use the translation tool for language conversion.",
|
|
287
|
-
[translate_tool_obj]
|
|
290
|
+
[translate_tool_obj],
|
|
288
291
|
)
|
|
289
292
|
|
|
290
293
|
# General Assistant (no specific tools)
|
|
@@ -292,14 +295,14 @@ def create_example_agents():
|
|
|
292
295
|
"Assistant",
|
|
293
296
|
"A general-purpose helpful assistant",
|
|
294
297
|
"You are a helpful, friendly assistant ready to help with various tasks.",
|
|
295
|
-
[]
|
|
298
|
+
[],
|
|
296
299
|
)
|
|
297
300
|
|
|
298
301
|
return {
|
|
299
302
|
"MathTutor": math_agent,
|
|
300
303
|
"WeatherBot": weather_agent,
|
|
301
304
|
"Translator": translation_agent,
|
|
302
|
-
"Assistant": general_agent
|
|
305
|
+
"Assistant": general_agent,
|
|
303
306
|
}
|
|
304
307
|
|
|
305
308
|
|
|
@@ -321,7 +324,7 @@ async def main():
|
|
|
321
324
|
name="JAF A2A Example Server",
|
|
322
325
|
description="Multi-agent server showcasing A2A protocol capabilities",
|
|
323
326
|
host="localhost",
|
|
324
|
-
port=3000
|
|
327
|
+
port=3000,
|
|
325
328
|
)
|
|
326
329
|
|
|
327
330
|
# Add mock model provider
|
jaf/a2a/memory/__init__.py
CHANGED
|
@@ -78,7 +78,6 @@ __all__ = [
|
|
|
78
78
|
"A2ATaskSerialized",
|
|
79
79
|
"A2ATaskCleanupConfig",
|
|
80
80
|
"CleanupResult",
|
|
81
|
-
|
|
82
81
|
# Factory functions
|
|
83
82
|
"create_a2a_task_error",
|
|
84
83
|
"create_a2a_task_not_found_error",
|
|
@@ -89,7 +88,6 @@ __all__ = [
|
|
|
89
88
|
"create_a2a_task_provider_from_env",
|
|
90
89
|
"create_simple_a2a_task_provider",
|
|
91
90
|
"create_composite_a2a_task_provider",
|
|
92
|
-
|
|
93
91
|
# Serialization functions
|
|
94
92
|
"serialize_a2a_task",
|
|
95
93
|
"deserialize_a2a_task",
|
|
@@ -98,14 +96,12 @@ __all__ = [
|
|
|
98
96
|
"validate_task_integrity",
|
|
99
97
|
"clone_task",
|
|
100
98
|
"sanitize_task",
|
|
101
|
-
|
|
102
99
|
# Cleanup functions
|
|
103
100
|
"default_cleanup_config",
|
|
104
101
|
"perform_task_cleanup",
|
|
105
102
|
"create_task_cleanup_scheduler",
|
|
106
103
|
"validate_cleanup_config",
|
|
107
104
|
"create_cleanup_config_from_env",
|
|
108
|
-
|
|
109
105
|
# Validation functions
|
|
110
106
|
"validate_a2a_task_provider_config",
|
|
111
107
|
"is_a2a_task_error",
|
jaf/a2a/memory/cleanup.py
CHANGED
|
@@ -106,9 +106,7 @@ async def perform_task_cleanup(
|
|
|
106
106
|
)
|
|
107
107
|
|
|
108
108
|
if not config.enabled:
|
|
109
|
-
return create_a2a_success(
|
|
110
|
-
CleanupResult(0, 0, 0, 0, [], 0)
|
|
111
|
-
)
|
|
109
|
+
return create_a2a_success(CleanupResult(0, 0, 0, 0, [], 0))
|
|
112
110
|
|
|
113
111
|
errors: List[str] = []
|
|
114
112
|
tasks_to_delete: Set[str] = set()
|
|
@@ -116,7 +114,7 @@ async def perform_task_cleanup(
|
|
|
116
114
|
# 1. Age-based cleanup
|
|
117
115
|
if config.max_age is not None:
|
|
118
116
|
cutoff_date = datetime.now(timezone.utc) - timedelta(seconds=config.max_age)
|
|
119
|
-
|
|
117
|
+
|
|
120
118
|
# Find all tasks older than the cutoff date
|
|
121
119
|
# We query all states and filter out the ones to retain
|
|
122
120
|
try:
|
|
@@ -124,7 +122,7 @@ async def perform_task_cleanup(
|
|
|
124
122
|
A2ATaskQuery(
|
|
125
123
|
context_id=context_id,
|
|
126
124
|
until=cutoff_date,
|
|
127
|
-
limit=10000
|
|
125
|
+
limit=10000, # A high limit to get a large batch
|
|
128
126
|
)
|
|
129
127
|
)
|
|
130
128
|
|
|
@@ -145,18 +143,27 @@ async def perform_task_cleanup(
|
|
|
145
143
|
if config.max_completed_tasks is not None:
|
|
146
144
|
try:
|
|
147
145
|
completed_tasks_result = await task_provider.find_tasks(
|
|
148
|
-
A2ATaskQuery(
|
|
146
|
+
A2ATaskQuery(
|
|
147
|
+
state=TaskState.COMPLETED,
|
|
148
|
+
context_id=context_id,
|
|
149
|
+
limit=config.max_completed_tasks + 200,
|
|
150
|
+
)
|
|
149
151
|
)
|
|
150
152
|
if completed_tasks_result.data:
|
|
151
153
|
tasks = [t for t in completed_tasks_result.data if t.id not in tasks_to_delete]
|
|
152
154
|
if len(tasks) > config.max_completed_tasks:
|
|
153
|
-
tasks.sort(
|
|
155
|
+
tasks.sort(
|
|
156
|
+
key=lambda t: t.status.timestamp
|
|
157
|
+
or datetime.min.replace(tzinfo=timezone.utc)
|
|
158
|
+
)
|
|
154
159
|
num_to_delete = len(tasks) - config.max_completed_tasks
|
|
155
160
|
for i in range(num_to_delete):
|
|
156
161
|
tasks_to_delete.add(tasks[i].id)
|
|
157
162
|
excess_completed_cleaned += 1
|
|
158
163
|
elif completed_tasks_result.error:
|
|
159
|
-
errors.append(
|
|
164
|
+
errors.append(
|
|
165
|
+
f"Failed to query completed tasks: {completed_tasks_result.error.message}"
|
|
166
|
+
)
|
|
160
167
|
except Exception as e:
|
|
161
168
|
errors.append(f"Error during completed task count cleanup: {e!s}")
|
|
162
169
|
|
|
@@ -165,12 +172,19 @@ async def perform_task_cleanup(
|
|
|
165
172
|
if config.max_failed_tasks is not None:
|
|
166
173
|
try:
|
|
167
174
|
failed_tasks_result = await task_provider.find_tasks(
|
|
168
|
-
A2ATaskQuery(
|
|
175
|
+
A2ATaskQuery(
|
|
176
|
+
state=TaskState.FAILED,
|
|
177
|
+
context_id=context_id,
|
|
178
|
+
limit=config.max_failed_tasks + 200,
|
|
179
|
+
)
|
|
169
180
|
)
|
|
170
181
|
if failed_tasks_result.data:
|
|
171
182
|
tasks = [t for t in failed_tasks_result.data if t.id not in tasks_to_delete]
|
|
172
183
|
if len(tasks) > config.max_failed_tasks:
|
|
173
|
-
tasks.sort(
|
|
184
|
+
tasks.sort(
|
|
185
|
+
key=lambda t: t.status.timestamp
|
|
186
|
+
or datetime.min.replace(tzinfo=timezone.utc)
|
|
187
|
+
)
|
|
174
188
|
num_to_delete = len(tasks) - config.max_failed_tasks
|
|
175
189
|
for i in range(num_to_delete):
|
|
176
190
|
tasks_to_delete.add(tasks[i].id)
|
|
@@ -180,7 +194,6 @@ async def perform_task_cleanup(
|
|
|
180
194
|
except Exception as e:
|
|
181
195
|
errors.append(f"Error during failed task count cleanup: {e!s}")
|
|
182
196
|
|
|
183
|
-
|
|
184
197
|
if config.dry_run:
|
|
185
198
|
return create_a2a_success(
|
|
186
199
|
CleanupResult(
|
|
@@ -210,7 +223,7 @@ async def perform_task_cleanup(
|
|
|
210
223
|
# This is complex as we don't know which category succeeded.
|
|
211
224
|
# The initial counts are based on intent.
|
|
212
225
|
# For now, we return the intended breakdown and total actual deletions.
|
|
213
|
-
|
|
226
|
+
|
|
214
227
|
final_result = CleanupResult(
|
|
215
228
|
expired_cleaned=expired_cleaned,
|
|
216
229
|
excess_completed_cleaned=excess_completed_cleaned,
|
|
@@ -294,9 +307,7 @@ class TaskCleanupScheduler:
|
|
|
294
307
|
f"A2A task cleanup completed: {cleanup_result.total_cleaned} tasks cleaned"
|
|
295
308
|
)
|
|
296
309
|
if cleanup_result.errors:
|
|
297
|
-
print(
|
|
298
|
-
f"A2A task cleanup errors: {', '.join(cleanup_result.errors)}"
|
|
299
|
-
)
|
|
310
|
+
print(f"A2A task cleanup errors: {', '.join(cleanup_result.errors)}")
|
|
300
311
|
elif result.error:
|
|
301
312
|
print(f"A2A task cleanup failed: {result.error.message}")
|
|
302
313
|
|
|
@@ -318,18 +329,14 @@ def create_cleanup_config_from_env() -> A2ATaskCleanupConfig:
|
|
|
318
329
|
"""
|
|
319
330
|
Helper function to create cleanup config from environment variables
|
|
320
331
|
"""
|
|
321
|
-
retain_states_str = os.getenv(
|
|
322
|
-
"JAF_A2A_CLEANUP_RETAIN_STATES", "working,submitted"
|
|
323
|
-
)
|
|
332
|
+
retain_states_str = os.getenv("JAF_A2A_CLEANUP_RETAIN_STATES", "working,submitted")
|
|
324
333
|
retain_states = [s.strip() for s in retain_states_str.split(",") if s.strip()]
|
|
325
334
|
|
|
326
335
|
return A2ATaskCleanupConfig(
|
|
327
336
|
enabled=os.getenv("JAF_A2A_CLEANUP_ENABLED", "true").lower() == "true",
|
|
328
337
|
interval=int(os.getenv("JAF_A2A_CLEANUP_INTERVAL", "3600")),
|
|
329
338
|
max_age=int(os.getenv("JAF_A2A_CLEANUP_MAX_AGE", str(7 * 24 * 60 * 60))),
|
|
330
|
-
max_completed_tasks=int(
|
|
331
|
-
os.getenv("JAF_A2A_CLEANUP_MAX_COMPLETED", "1000")
|
|
332
|
-
),
|
|
339
|
+
max_completed_tasks=int(os.getenv("JAF_A2A_CLEANUP_MAX_COMPLETED", "1000")),
|
|
333
340
|
max_failed_tasks=int(os.getenv("JAF_A2A_CLEANUP_MAX_FAILED", "500")),
|
|
334
341
|
retain_states=retain_states,
|
|
335
342
|
batch_size=int(os.getenv("JAF_A2A_CLEANUP_BATCH_SIZE", "100")),
|