agentfield 0.1.22rc2__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.
- agentfield/__init__.py +66 -0
- agentfield/agent.py +3569 -0
- agentfield/agent_ai.py +1125 -0
- agentfield/agent_cli.py +386 -0
- agentfield/agent_field_handler.py +494 -0
- agentfield/agent_mcp.py +534 -0
- agentfield/agent_registry.py +29 -0
- agentfield/agent_server.py +1185 -0
- agentfield/agent_utils.py +269 -0
- agentfield/agent_workflow.py +323 -0
- agentfield/async_config.py +278 -0
- agentfield/async_execution_manager.py +1227 -0
- agentfield/client.py +1447 -0
- agentfield/connection_manager.py +280 -0
- agentfield/decorators.py +527 -0
- agentfield/did_manager.py +337 -0
- agentfield/dynamic_skills.py +304 -0
- agentfield/execution_context.py +255 -0
- agentfield/execution_state.py +453 -0
- agentfield/http_connection_manager.py +429 -0
- agentfield/litellm_adapters.py +140 -0
- agentfield/logger.py +249 -0
- agentfield/mcp_client.py +204 -0
- agentfield/mcp_manager.py +340 -0
- agentfield/mcp_stdio_bridge.py +550 -0
- agentfield/memory.py +723 -0
- agentfield/memory_events.py +489 -0
- agentfield/multimodal.py +173 -0
- agentfield/multimodal_response.py +403 -0
- agentfield/pydantic_utils.py +227 -0
- agentfield/rate_limiter.py +280 -0
- agentfield/result_cache.py +441 -0
- agentfield/router.py +190 -0
- agentfield/status.py +70 -0
- agentfield/types.py +710 -0
- agentfield/utils.py +26 -0
- agentfield/vc_generator.py +464 -0
- agentfield/vision.py +198 -0
- agentfield-0.1.22rc2.dist-info/METADATA +102 -0
- agentfield-0.1.22rc2.dist-info/RECORD +42 -0
- agentfield-0.1.22rc2.dist-info/WHEEL +5 -0
- agentfield-0.1.22rc2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AgentField SDK Connection Manager
|
|
3
|
+
|
|
4
|
+
Provides resilient connection handling for AgentField server connectivity.
|
|
5
|
+
Handles automatic reconnection, graceful degradation, and connection health monitoring.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import time
|
|
10
|
+
from enum import Enum
|
|
11
|
+
from typing import Optional, Callable, Any, Dict
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from agentfield.logger import log_debug, log_info, log_warn, log_error
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ConnectionState(Enum):
|
|
17
|
+
"""Connection states for AgentField server connectivity"""
|
|
18
|
+
|
|
19
|
+
DISCONNECTED = "disconnected"
|
|
20
|
+
CONNECTING = "connecting"
|
|
21
|
+
CONNECTED = "connected"
|
|
22
|
+
RECONNECTING = "reconnecting"
|
|
23
|
+
DEGRADED = "degraded" # Running locally without AgentField server
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class ConnectionConfig:
|
|
28
|
+
"""Configuration for connection management"""
|
|
29
|
+
|
|
30
|
+
retry_interval: float = 10.0 # Consistent retry interval in seconds
|
|
31
|
+
health_check_interval: float = 30.0 # Health check interval in seconds
|
|
32
|
+
connection_timeout: float = 10.0 # Connection timeout in seconds
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ConnectionManager:
|
|
36
|
+
"""
|
|
37
|
+
Manages resilient connections to AgentField server with automatic reconnection,
|
|
38
|
+
graceful degradation, and health monitoring.
|
|
39
|
+
|
|
40
|
+
Uses a simple, consistent retry interval to ensure immediate reconnection
|
|
41
|
+
when AgentField server becomes available.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, agent, config: Optional[ConnectionConfig] = None):
|
|
45
|
+
self.agent = agent
|
|
46
|
+
self.config = config or ConnectionConfig()
|
|
47
|
+
|
|
48
|
+
# Connection state
|
|
49
|
+
self.state = ConnectionState.DISCONNECTED
|
|
50
|
+
self.last_successful_connection = None
|
|
51
|
+
|
|
52
|
+
# Tasks
|
|
53
|
+
self._reconnection_task: Optional[asyncio.Task] = None
|
|
54
|
+
self._health_check_task: Optional[asyncio.Task] = None
|
|
55
|
+
self._shutdown_requested = False
|
|
56
|
+
|
|
57
|
+
# Callbacks
|
|
58
|
+
self.on_connected: Optional[Callable] = None
|
|
59
|
+
self.on_disconnected: Optional[Callable] = None
|
|
60
|
+
self.on_degraded: Optional[Callable] = None
|
|
61
|
+
|
|
62
|
+
async def start(self) -> bool:
|
|
63
|
+
"""
|
|
64
|
+
Start the connection manager and attempt initial connection.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
True if initial connection successful, False if entering degraded mode
|
|
68
|
+
"""
|
|
69
|
+
log_info("Starting connection manager")
|
|
70
|
+
|
|
71
|
+
# Attempt initial connection
|
|
72
|
+
success = await self._attempt_connection()
|
|
73
|
+
|
|
74
|
+
if success:
|
|
75
|
+
self._on_connection_success()
|
|
76
|
+
# Start health monitoring
|
|
77
|
+
self._health_check_task = asyncio.create_task(self._health_check_loop())
|
|
78
|
+
else:
|
|
79
|
+
self._on_connection_failure()
|
|
80
|
+
# Start reconnection attempts
|
|
81
|
+
self._reconnection_task = asyncio.create_task(self._reconnection_loop())
|
|
82
|
+
|
|
83
|
+
return success
|
|
84
|
+
|
|
85
|
+
async def stop(self):
|
|
86
|
+
"""Stop the connection manager and cleanup tasks"""
|
|
87
|
+
log_info("Stopping connection manager")
|
|
88
|
+
self._shutdown_requested = True
|
|
89
|
+
|
|
90
|
+
# Cancel tasks
|
|
91
|
+
if self._reconnection_task and not self._reconnection_task.done():
|
|
92
|
+
self._reconnection_task.cancel()
|
|
93
|
+
try:
|
|
94
|
+
await self._reconnection_task
|
|
95
|
+
except asyncio.CancelledError:
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
if self._health_check_task and not self._health_check_task.done():
|
|
99
|
+
self._health_check_task.cancel()
|
|
100
|
+
try:
|
|
101
|
+
await self._health_check_task
|
|
102
|
+
except asyncio.CancelledError:
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
async def _attempt_connection(self) -> bool:
|
|
106
|
+
"""
|
|
107
|
+
Attempt to connect to AgentField server.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
True if connection successful, False otherwise
|
|
111
|
+
"""
|
|
112
|
+
try:
|
|
113
|
+
self.state = ConnectionState.CONNECTING
|
|
114
|
+
log_debug("Attempting connection to AgentField server")
|
|
115
|
+
|
|
116
|
+
# Try to register with AgentField server - suppress verbose error logging
|
|
117
|
+
import logging
|
|
118
|
+
|
|
119
|
+
# Temporarily suppress httpx and httpcore logging to avoid verbose connection errors
|
|
120
|
+
httpx_logger = logging.getLogger("httpx")
|
|
121
|
+
httpcore_logger = logging.getLogger("httpcore")
|
|
122
|
+
original_httpx_level = httpx_logger.level
|
|
123
|
+
original_httpcore_level = httpcore_logger.level
|
|
124
|
+
|
|
125
|
+
# Set to ERROR level to suppress connection attempt logs
|
|
126
|
+
httpx_logger.setLevel(logging.ERROR)
|
|
127
|
+
httpcore_logger.setLevel(logging.ERROR)
|
|
128
|
+
|
|
129
|
+
discovery_payload = self.agent._build_callback_discovery_payload()
|
|
130
|
+
|
|
131
|
+
success = False
|
|
132
|
+
payload: Optional[Dict[str, Any]] = None
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
success, payload = await self.agent.client.register_agent_with_status(
|
|
136
|
+
node_id=self.agent.node_id,
|
|
137
|
+
reasoners=self.agent.reasoners,
|
|
138
|
+
skills=self.agent.skills,
|
|
139
|
+
base_url=self.agent.base_url,
|
|
140
|
+
status=self.agent._current_status,
|
|
141
|
+
discovery=discovery_payload,
|
|
142
|
+
suppress_errors=True, # Suppress verbose error logging for connection attempts
|
|
143
|
+
vc_metadata=self.agent._build_vc_metadata(),
|
|
144
|
+
)
|
|
145
|
+
finally:
|
|
146
|
+
# Restore original logging levels
|
|
147
|
+
httpx_logger.setLevel(original_httpx_level)
|
|
148
|
+
httpcore_logger.setLevel(original_httpcore_level)
|
|
149
|
+
|
|
150
|
+
if success:
|
|
151
|
+
if payload:
|
|
152
|
+
self.agent._apply_discovery_response(payload)
|
|
153
|
+
if self.agent.did_manager and not self.agent.did_enabled:
|
|
154
|
+
self.agent._register_agent_with_did()
|
|
155
|
+
self.state = ConnectionState.CONNECTED
|
|
156
|
+
return True
|
|
157
|
+
else:
|
|
158
|
+
self.state = ConnectionState.DISCONNECTED
|
|
159
|
+
return False
|
|
160
|
+
|
|
161
|
+
except Exception as e:
|
|
162
|
+
# Only log at debug level to avoid spam
|
|
163
|
+
log_debug(f"Connection attempt failed: {type(e).__name__}")
|
|
164
|
+
self.state = ConnectionState.DISCONNECTED
|
|
165
|
+
return False
|
|
166
|
+
|
|
167
|
+
async def _health_check_loop(self):
|
|
168
|
+
"""Background loop for monitoring connection health"""
|
|
169
|
+
while not self._shutdown_requested and self.state == ConnectionState.CONNECTED:
|
|
170
|
+
try:
|
|
171
|
+
await asyncio.sleep(self.config.health_check_interval)
|
|
172
|
+
|
|
173
|
+
if self._shutdown_requested:
|
|
174
|
+
break
|
|
175
|
+
|
|
176
|
+
# Try to send a heartbeat to check connection health
|
|
177
|
+
success = await self.agent.agentfield_handler.send_enhanced_heartbeat()
|
|
178
|
+
|
|
179
|
+
if not success:
|
|
180
|
+
log_warn("Health check failed - connection lost")
|
|
181
|
+
self._on_connection_failure()
|
|
182
|
+
# Start reconnection attempts
|
|
183
|
+
self._reconnection_task = asyncio.create_task(
|
|
184
|
+
self._reconnection_loop()
|
|
185
|
+
)
|
|
186
|
+
break
|
|
187
|
+
|
|
188
|
+
except asyncio.CancelledError:
|
|
189
|
+
break
|
|
190
|
+
except Exception as e:
|
|
191
|
+
log_error(f"Health check error: {e}")
|
|
192
|
+
self._on_connection_failure()
|
|
193
|
+
# Start reconnection attempts
|
|
194
|
+
self._reconnection_task = asyncio.create_task(self._reconnection_loop())
|
|
195
|
+
break
|
|
196
|
+
|
|
197
|
+
async def _reconnection_loop(self):
|
|
198
|
+
"""Background loop for attempting reconnection"""
|
|
199
|
+
self.state = ConnectionState.RECONNECTING
|
|
200
|
+
|
|
201
|
+
while not self._shutdown_requested and self.state != ConnectionState.CONNECTED:
|
|
202
|
+
try:
|
|
203
|
+
log_debug(
|
|
204
|
+
f"Attempting reconnection in {self.config.retry_interval} seconds..."
|
|
205
|
+
)
|
|
206
|
+
await asyncio.sleep(self.config.retry_interval)
|
|
207
|
+
|
|
208
|
+
if self._shutdown_requested:
|
|
209
|
+
break
|
|
210
|
+
|
|
211
|
+
success = await self._attempt_connection()
|
|
212
|
+
|
|
213
|
+
if success:
|
|
214
|
+
self._on_connection_success()
|
|
215
|
+
# Start health monitoring again
|
|
216
|
+
self._health_check_task = asyncio.create_task(
|
|
217
|
+
self._health_check_loop()
|
|
218
|
+
)
|
|
219
|
+
break
|
|
220
|
+
else:
|
|
221
|
+
log_debug("Reconnection attempt failed, will retry")
|
|
222
|
+
|
|
223
|
+
except asyncio.CancelledError:
|
|
224
|
+
break
|
|
225
|
+
except Exception as e:
|
|
226
|
+
log_error(f"Reconnection error: {e}")
|
|
227
|
+
# Continue trying
|
|
228
|
+
|
|
229
|
+
def _on_connection_success(self):
|
|
230
|
+
"""Handle successful connection"""
|
|
231
|
+
self.state = ConnectionState.CONNECTED
|
|
232
|
+
self.last_successful_connection = time.time()
|
|
233
|
+
self.agent.agentfield_connected = True
|
|
234
|
+
|
|
235
|
+
log_info("Connected to AgentField server")
|
|
236
|
+
|
|
237
|
+
if self.on_connected:
|
|
238
|
+
try:
|
|
239
|
+
self.on_connected()
|
|
240
|
+
except Exception as e:
|
|
241
|
+
log_error(f"Error in connection callback: {e}")
|
|
242
|
+
|
|
243
|
+
def _on_connection_failure(self):
|
|
244
|
+
"""Handle connection failure"""
|
|
245
|
+
self.state = ConnectionState.DEGRADED
|
|
246
|
+
self.agent.agentfield_connected = False
|
|
247
|
+
|
|
248
|
+
log_warn("AgentField server unavailable - running in degraded mode")
|
|
249
|
+
|
|
250
|
+
if self.on_disconnected:
|
|
251
|
+
try:
|
|
252
|
+
self.on_disconnected()
|
|
253
|
+
except Exception as e:
|
|
254
|
+
log_error(f"Error in disconnection callback: {e}")
|
|
255
|
+
|
|
256
|
+
def is_connected(self) -> bool:
|
|
257
|
+
"""Check if currently connected to AgentField server"""
|
|
258
|
+
return self.state == ConnectionState.CONNECTED
|
|
259
|
+
|
|
260
|
+
def is_degraded(self) -> bool:
|
|
261
|
+
"""Check if running in degraded mode"""
|
|
262
|
+
return self.state == ConnectionState.DEGRADED
|
|
263
|
+
|
|
264
|
+
async def force_reconnect(self):
|
|
265
|
+
"""Force an immediate reconnection attempt"""
|
|
266
|
+
if self.state == ConnectionState.CONNECTED:
|
|
267
|
+
return True
|
|
268
|
+
|
|
269
|
+
log_info("Forcing reconnection attempt")
|
|
270
|
+
success = await self._attempt_connection()
|
|
271
|
+
|
|
272
|
+
if success:
|
|
273
|
+
self._on_connection_success()
|
|
274
|
+
# Cancel existing reconnection task if running
|
|
275
|
+
if self._reconnection_task and not self._reconnection_task.done():
|
|
276
|
+
self._reconnection_task.cancel()
|
|
277
|
+
# Start health monitoring
|
|
278
|
+
self._health_check_task = asyncio.create_task(self._health_check_loop())
|
|
279
|
+
|
|
280
|
+
return success
|