aiecs 1.3.8__py3-none-any.whl → 1.4.0__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.
Potentially problematic release.
This version of aiecs might be problematic. Click here for more details.
- aiecs/__init__.py +1 -1
- aiecs/domain/__init__.py +120 -0
- aiecs/domain/agent/__init__.py +184 -0
- aiecs/domain/agent/base_agent.py +691 -0
- aiecs/domain/agent/exceptions.py +99 -0
- aiecs/domain/agent/hybrid_agent.py +495 -0
- aiecs/domain/agent/integration/__init__.py +23 -0
- aiecs/domain/agent/integration/context_compressor.py +219 -0
- aiecs/domain/agent/integration/context_engine_adapter.py +258 -0
- aiecs/domain/agent/integration/retry_policy.py +228 -0
- aiecs/domain/agent/integration/role_config.py +217 -0
- aiecs/domain/agent/lifecycle.py +298 -0
- aiecs/domain/agent/llm_agent.py +309 -0
- aiecs/domain/agent/memory/__init__.py +13 -0
- aiecs/domain/agent/memory/conversation.py +216 -0
- aiecs/domain/agent/migration/__init__.py +15 -0
- aiecs/domain/agent/migration/conversion.py +171 -0
- aiecs/domain/agent/migration/legacy_wrapper.py +97 -0
- aiecs/domain/agent/models.py +263 -0
- aiecs/domain/agent/observability.py +443 -0
- aiecs/domain/agent/persistence.py +287 -0
- aiecs/domain/agent/prompts/__init__.py +25 -0
- aiecs/domain/agent/prompts/builder.py +164 -0
- aiecs/domain/agent/prompts/formatters.py +192 -0
- aiecs/domain/agent/prompts/template.py +264 -0
- aiecs/domain/agent/registry.py +261 -0
- aiecs/domain/agent/tool_agent.py +267 -0
- aiecs/domain/agent/tools/__init__.py +13 -0
- aiecs/domain/agent/tools/schema_generator.py +222 -0
- aiecs/main.py +2 -2
- {aiecs-1.3.8.dist-info → aiecs-1.4.0.dist-info}/METADATA +1 -1
- {aiecs-1.3.8.dist-info → aiecs-1.4.0.dist-info}/RECORD +36 -9
- {aiecs-1.3.8.dist-info → aiecs-1.4.0.dist-info}/WHEEL +0 -0
- {aiecs-1.3.8.dist-info → aiecs-1.4.0.dist-info}/entry_points.txt +0 -0
- {aiecs-1.3.8.dist-info → aiecs-1.4.0.dist-info}/licenses/LICENSE +0 -0
- {aiecs-1.3.8.dist-info → aiecs-1.4.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base AI Agent
|
|
3
|
+
|
|
4
|
+
Abstract base class for all AI agents in the AIECS system.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import uuid
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from typing import Dict, List, Any, Optional, Callable
|
|
11
|
+
import logging
|
|
12
|
+
|
|
13
|
+
from .models import (
|
|
14
|
+
AgentState,
|
|
15
|
+
AgentType,
|
|
16
|
+
AgentConfiguration,
|
|
17
|
+
AgentGoal,
|
|
18
|
+
AgentMetrics,
|
|
19
|
+
AgentCapabilityDeclaration,
|
|
20
|
+
AgentMemory,
|
|
21
|
+
GoalStatus,
|
|
22
|
+
GoalPriority,
|
|
23
|
+
MemoryType,
|
|
24
|
+
)
|
|
25
|
+
from .exceptions import (
|
|
26
|
+
InvalidStateTransitionError,
|
|
27
|
+
ConfigurationError,
|
|
28
|
+
AgentInitializationError,
|
|
29
|
+
SerializationError,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class BaseAIAgent(ABC):
|
|
36
|
+
"""
|
|
37
|
+
Abstract base class for AI agents.
|
|
38
|
+
|
|
39
|
+
Provides common functionality for agent lifecycle management,
|
|
40
|
+
state management, memory, goals, and metrics tracking.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
agent_id: str,
|
|
46
|
+
name: str,
|
|
47
|
+
agent_type: AgentType,
|
|
48
|
+
config: AgentConfiguration,
|
|
49
|
+
description: Optional[str] = None,
|
|
50
|
+
version: str = "1.0.0",
|
|
51
|
+
):
|
|
52
|
+
"""
|
|
53
|
+
Initialize the base agent.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
agent_id: Unique identifier for the agent
|
|
57
|
+
name: Agent name
|
|
58
|
+
agent_type: Type of agent
|
|
59
|
+
config: Agent configuration
|
|
60
|
+
description: Optional agent description
|
|
61
|
+
version: Agent version
|
|
62
|
+
"""
|
|
63
|
+
# Identity
|
|
64
|
+
self.agent_id = agent_id
|
|
65
|
+
self.name = name
|
|
66
|
+
self.agent_type = agent_type
|
|
67
|
+
self.description = description or f"{agent_type.value} agent"
|
|
68
|
+
self.version = version
|
|
69
|
+
|
|
70
|
+
# Configuration
|
|
71
|
+
self._config = config
|
|
72
|
+
|
|
73
|
+
# State
|
|
74
|
+
self._state = AgentState.CREATED
|
|
75
|
+
self._previous_state: Optional[AgentState] = None
|
|
76
|
+
|
|
77
|
+
# Memory storage (in-memory dict, can be replaced with sophisticated storage)
|
|
78
|
+
self._memory: Dict[str, Any] = {}
|
|
79
|
+
self._memory_metadata: Dict[str, Dict[str, Any]] = {}
|
|
80
|
+
|
|
81
|
+
# Goals
|
|
82
|
+
self._goals: Dict[str, AgentGoal] = {}
|
|
83
|
+
|
|
84
|
+
# Capabilities
|
|
85
|
+
self._capabilities: Dict[str, AgentCapabilityDeclaration] = {}
|
|
86
|
+
|
|
87
|
+
# Metrics
|
|
88
|
+
self._metrics = AgentMetrics()
|
|
89
|
+
|
|
90
|
+
# Timestamps
|
|
91
|
+
self.created_at = datetime.utcnow()
|
|
92
|
+
self.updated_at = datetime.utcnow()
|
|
93
|
+
self.last_active_at: Optional[datetime] = None
|
|
94
|
+
|
|
95
|
+
# Current task tracking
|
|
96
|
+
self._current_task_id: Optional[str] = None
|
|
97
|
+
|
|
98
|
+
logger.info(f"Agent initialized: {self.agent_id} ({self.name}, {self.agent_type.value})")
|
|
99
|
+
|
|
100
|
+
# ==================== State Management ====================
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def state(self) -> AgentState:
|
|
104
|
+
"""Get current agent state."""
|
|
105
|
+
return self._state
|
|
106
|
+
|
|
107
|
+
def get_state(self) -> AgentState:
|
|
108
|
+
"""Get current agent state."""
|
|
109
|
+
return self._state
|
|
110
|
+
|
|
111
|
+
def _transition_state(self, new_state: AgentState) -> None:
|
|
112
|
+
"""
|
|
113
|
+
Transition to a new state with validation.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
new_state: Target state
|
|
117
|
+
|
|
118
|
+
Raises:
|
|
119
|
+
InvalidStateTransitionError: If transition is invalid
|
|
120
|
+
"""
|
|
121
|
+
# Define valid transitions
|
|
122
|
+
valid_transitions = {
|
|
123
|
+
AgentState.CREATED: {AgentState.INITIALIZING},
|
|
124
|
+
AgentState.INITIALIZING: {AgentState.ACTIVE, AgentState.ERROR},
|
|
125
|
+
AgentState.ACTIVE: {AgentState.BUSY, AgentState.IDLE, AgentState.STOPPED, AgentState.ERROR},
|
|
126
|
+
AgentState.BUSY: {AgentState.ACTIVE, AgentState.ERROR},
|
|
127
|
+
AgentState.IDLE: {AgentState.ACTIVE, AgentState.STOPPED},
|
|
128
|
+
AgentState.ERROR: {AgentState.ACTIVE, AgentState.STOPPED},
|
|
129
|
+
AgentState.STOPPED: set(), # Terminal state
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if new_state not in valid_transitions.get(self._state, set()):
|
|
133
|
+
raise InvalidStateTransitionError(
|
|
134
|
+
agent_id=self.agent_id,
|
|
135
|
+
current_state=self._state.value,
|
|
136
|
+
attempted_state=new_state.value,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
self._previous_state = self._state
|
|
140
|
+
self._state = new_state
|
|
141
|
+
self.updated_at = datetime.utcnow()
|
|
142
|
+
|
|
143
|
+
logger.info(f"Agent {self.agent_id} state: {self._previous_state.value} → {new_state.value}")
|
|
144
|
+
|
|
145
|
+
# ==================== Lifecycle Methods ====================
|
|
146
|
+
|
|
147
|
+
async def initialize(self) -> None:
|
|
148
|
+
"""
|
|
149
|
+
Initialize the agent.
|
|
150
|
+
|
|
151
|
+
This method should be called before the agent can be used.
|
|
152
|
+
Override in subclasses to add initialization logic.
|
|
153
|
+
|
|
154
|
+
Raises:
|
|
155
|
+
AgentInitializationError: If initialization fails
|
|
156
|
+
"""
|
|
157
|
+
try:
|
|
158
|
+
self._transition_state(AgentState.INITIALIZING)
|
|
159
|
+
logger.info(f"Initializing agent {self.agent_id}...")
|
|
160
|
+
|
|
161
|
+
# Subclass initialization
|
|
162
|
+
await self._initialize()
|
|
163
|
+
|
|
164
|
+
self._transition_state(AgentState.ACTIVE)
|
|
165
|
+
self.last_active_at = datetime.utcnow()
|
|
166
|
+
logger.info(f"Agent {self.agent_id} initialized successfully")
|
|
167
|
+
|
|
168
|
+
except Exception as e:
|
|
169
|
+
self._transition_state(AgentState.ERROR)
|
|
170
|
+
logger.error(f"Agent {self.agent_id} initialization failed: {e}")
|
|
171
|
+
raise AgentInitializationError(
|
|
172
|
+
f"Failed to initialize agent {self.agent_id}: {str(e)}",
|
|
173
|
+
agent_id=self.agent_id,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
@abstractmethod
|
|
177
|
+
async def _initialize(self) -> None:
|
|
178
|
+
"""
|
|
179
|
+
Subclass-specific initialization logic.
|
|
180
|
+
|
|
181
|
+
Override this method in subclasses to implement
|
|
182
|
+
custom initialization.
|
|
183
|
+
"""
|
|
184
|
+
pass
|
|
185
|
+
|
|
186
|
+
async def activate(self) -> None:
|
|
187
|
+
"""Activate the agent."""
|
|
188
|
+
if self._state == AgentState.IDLE:
|
|
189
|
+
self._transition_state(AgentState.ACTIVE)
|
|
190
|
+
self.last_active_at = datetime.utcnow()
|
|
191
|
+
logger.info(f"Agent {self.agent_id} activated")
|
|
192
|
+
else:
|
|
193
|
+
logger.warning(
|
|
194
|
+
f"Agent {self.agent_id} cannot be activated from state {self._state.value}"
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
async def deactivate(self) -> None:
|
|
198
|
+
"""Deactivate the agent (enter idle state)."""
|
|
199
|
+
if self._state == AgentState.ACTIVE:
|
|
200
|
+
self._transition_state(AgentState.IDLE)
|
|
201
|
+
logger.info(f"Agent {self.agent_id} deactivated")
|
|
202
|
+
else:
|
|
203
|
+
logger.warning(
|
|
204
|
+
f"Agent {self.agent_id} cannot be deactivated from state {self._state.value}"
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
async def shutdown(self) -> None:
|
|
208
|
+
"""
|
|
209
|
+
Shutdown the agent.
|
|
210
|
+
|
|
211
|
+
Override in subclasses to add cleanup logic.
|
|
212
|
+
"""
|
|
213
|
+
logger.info(f"Shutting down agent {self.agent_id}...")
|
|
214
|
+
await self._shutdown()
|
|
215
|
+
self._transition_state(AgentState.STOPPED)
|
|
216
|
+
logger.info(f"Agent {self.agent_id} shut down")
|
|
217
|
+
|
|
218
|
+
@abstractmethod
|
|
219
|
+
async def _shutdown(self) -> None:
|
|
220
|
+
"""
|
|
221
|
+
Subclass-specific shutdown logic.
|
|
222
|
+
|
|
223
|
+
Override this method in subclasses to implement
|
|
224
|
+
custom cleanup.
|
|
225
|
+
"""
|
|
226
|
+
pass
|
|
227
|
+
|
|
228
|
+
# ==================== Abstract Execution Methods ====================
|
|
229
|
+
|
|
230
|
+
@abstractmethod
|
|
231
|
+
async def execute_task(self, task: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]:
|
|
232
|
+
"""
|
|
233
|
+
Execute a task.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
task: Task specification
|
|
237
|
+
context: Execution context
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
Task execution result
|
|
241
|
+
|
|
242
|
+
Raises:
|
|
243
|
+
TaskExecutionError: If task execution fails
|
|
244
|
+
|
|
245
|
+
Note:
|
|
246
|
+
Subclasses can use `_execute_with_retry()` to wrap task execution
|
|
247
|
+
with automatic retry logic based on agent configuration.
|
|
248
|
+
"""
|
|
249
|
+
pass
|
|
250
|
+
|
|
251
|
+
@abstractmethod
|
|
252
|
+
async def process_message(self, message: str, sender_id: Optional[str] = None) -> Dict[str, Any]:
|
|
253
|
+
"""
|
|
254
|
+
Process an incoming message.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
message: Message content
|
|
258
|
+
sender_id: Optional sender identifier
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
Response dictionary
|
|
262
|
+
|
|
263
|
+
Note:
|
|
264
|
+
Subclasses can use `_execute_with_retry()` to wrap message processing
|
|
265
|
+
with automatic retry logic based on agent configuration.
|
|
266
|
+
"""
|
|
267
|
+
pass
|
|
268
|
+
|
|
269
|
+
# ==================== Retry Logic Integration ====================
|
|
270
|
+
|
|
271
|
+
async def _execute_with_retry(
|
|
272
|
+
self,
|
|
273
|
+
func: Callable,
|
|
274
|
+
*args,
|
|
275
|
+
**kwargs
|
|
276
|
+
) -> Any:
|
|
277
|
+
"""
|
|
278
|
+
Execute a function with retry logic using agent's retry policy.
|
|
279
|
+
|
|
280
|
+
This helper method wraps function execution with automatic retry based on
|
|
281
|
+
the agent's configuration. It uses EnhancedRetryPolicy for sophisticated
|
|
282
|
+
error handling with exponential backoff and error classification.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
func: Async function to execute
|
|
286
|
+
*args: Function positional arguments
|
|
287
|
+
**kwargs: Function keyword arguments
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
Function result
|
|
291
|
+
|
|
292
|
+
Raises:
|
|
293
|
+
Exception: If all retries are exhausted
|
|
294
|
+
|
|
295
|
+
Example:
|
|
296
|
+
```python
|
|
297
|
+
async def _execute_task_internal(self, task, context):
|
|
298
|
+
# Actual task execution logic
|
|
299
|
+
return result
|
|
300
|
+
|
|
301
|
+
async def execute_task(self, task, context):
|
|
302
|
+
return await self._execute_with_retry(
|
|
303
|
+
self._execute_task_internal,
|
|
304
|
+
task,
|
|
305
|
+
context
|
|
306
|
+
)
|
|
307
|
+
```
|
|
308
|
+
"""
|
|
309
|
+
from .integration.retry_policy import EnhancedRetryPolicy
|
|
310
|
+
|
|
311
|
+
# Get retry policy from configuration
|
|
312
|
+
retry_config = self._config.retry_policy
|
|
313
|
+
|
|
314
|
+
# Create retry policy instance
|
|
315
|
+
retry_policy = EnhancedRetryPolicy(
|
|
316
|
+
max_retries=retry_config.max_retries,
|
|
317
|
+
base_delay=retry_config.base_delay,
|
|
318
|
+
max_delay=retry_config.max_delay,
|
|
319
|
+
exponential_base=retry_config.exponential_factor,
|
|
320
|
+
jitter=retry_config.jitter_factor > 0
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
# Execute with retry
|
|
324
|
+
return await retry_policy.execute_with_retry(func, *args, **kwargs)
|
|
325
|
+
|
|
326
|
+
# ==================== Memory Management ====================
|
|
327
|
+
|
|
328
|
+
async def add_to_memory(
|
|
329
|
+
self,
|
|
330
|
+
key: str,
|
|
331
|
+
value: Any,
|
|
332
|
+
memory_type: MemoryType = MemoryType.SHORT_TERM,
|
|
333
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
334
|
+
) -> None:
|
|
335
|
+
"""
|
|
336
|
+
Add an item to agent memory.
|
|
337
|
+
|
|
338
|
+
Args:
|
|
339
|
+
key: Memory key
|
|
340
|
+
value: Memory value
|
|
341
|
+
memory_type: Type of memory (short_term or long_term)
|
|
342
|
+
metadata: Optional metadata
|
|
343
|
+
"""
|
|
344
|
+
self._memory[key] = value
|
|
345
|
+
self._memory_metadata[key] = {
|
|
346
|
+
"type": memory_type.value,
|
|
347
|
+
"timestamp": datetime.utcnow(),
|
|
348
|
+
"metadata": metadata or {},
|
|
349
|
+
}
|
|
350
|
+
logger.debug(f"Agent {self.agent_id} added memory: {key} ({memory_type.value})")
|
|
351
|
+
|
|
352
|
+
async def retrieve_memory(
|
|
353
|
+
self, key: str, default: Any = None
|
|
354
|
+
) -> Any:
|
|
355
|
+
"""
|
|
356
|
+
Retrieve an item from memory.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
key: Memory key
|
|
360
|
+
default: Default value if key not found
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
Memory value or default
|
|
364
|
+
"""
|
|
365
|
+
return self._memory.get(key, default)
|
|
366
|
+
|
|
367
|
+
async def clear_memory(self, memory_type: Optional[MemoryType] = None) -> None:
|
|
368
|
+
"""
|
|
369
|
+
Clear agent memory.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
memory_type: If specified, clear only this type of memory
|
|
373
|
+
"""
|
|
374
|
+
if memory_type is None:
|
|
375
|
+
self._memory.clear()
|
|
376
|
+
self._memory_metadata.clear()
|
|
377
|
+
logger.info(f"Agent {self.agent_id} cleared all memory")
|
|
378
|
+
else:
|
|
379
|
+
keys_to_remove = [
|
|
380
|
+
k
|
|
381
|
+
for k, v in self._memory_metadata.items()
|
|
382
|
+
if v.get("type") == memory_type.value
|
|
383
|
+
]
|
|
384
|
+
for key in keys_to_remove:
|
|
385
|
+
del self._memory[key]
|
|
386
|
+
del self._memory_metadata[key]
|
|
387
|
+
logger.info(f"Agent {self.agent_id} cleared {memory_type.value} memory")
|
|
388
|
+
|
|
389
|
+
def get_memory_summary(self) -> Dict[str, Any]:
|
|
390
|
+
"""Get a summary of agent memory."""
|
|
391
|
+
return {
|
|
392
|
+
"total_items": len(self._memory),
|
|
393
|
+
"short_term_count": sum(
|
|
394
|
+
1 for v in self._memory_metadata.values() if v.get("type") == MemoryType.SHORT_TERM.value
|
|
395
|
+
),
|
|
396
|
+
"long_term_count": sum(
|
|
397
|
+
1 for v in self._memory_metadata.values() if v.get("type") == MemoryType.LONG_TERM.value
|
|
398
|
+
),
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
# ==================== Goal Management ====================
|
|
402
|
+
|
|
403
|
+
def set_goal(
|
|
404
|
+
self,
|
|
405
|
+
description: str,
|
|
406
|
+
priority: GoalPriority = GoalPriority.MEDIUM,
|
|
407
|
+
success_criteria: Optional[str] = None,
|
|
408
|
+
deadline: Optional[datetime] = None,
|
|
409
|
+
) -> str:
|
|
410
|
+
"""
|
|
411
|
+
Set a new goal for the agent.
|
|
412
|
+
|
|
413
|
+
Args:
|
|
414
|
+
description: Goal description
|
|
415
|
+
priority: Goal priority
|
|
416
|
+
success_criteria: Success criteria
|
|
417
|
+
deadline: Goal deadline
|
|
418
|
+
|
|
419
|
+
Returns:
|
|
420
|
+
Goal ID
|
|
421
|
+
"""
|
|
422
|
+
goal = AgentGoal(
|
|
423
|
+
description=description,
|
|
424
|
+
priority=priority,
|
|
425
|
+
success_criteria=success_criteria,
|
|
426
|
+
deadline=deadline,
|
|
427
|
+
)
|
|
428
|
+
self._goals[goal.goal_id] = goal
|
|
429
|
+
logger.info(f"Agent {self.agent_id} set goal: {goal.goal_id} ({priority.value})")
|
|
430
|
+
return goal.goal_id
|
|
431
|
+
|
|
432
|
+
def get_goals(
|
|
433
|
+
self, status: Optional[GoalStatus] = None
|
|
434
|
+
) -> List[AgentGoal]:
|
|
435
|
+
"""
|
|
436
|
+
Get agent goals.
|
|
437
|
+
|
|
438
|
+
Args:
|
|
439
|
+
status: Filter by status (optional)
|
|
440
|
+
|
|
441
|
+
Returns:
|
|
442
|
+
List of goals
|
|
443
|
+
"""
|
|
444
|
+
if status is None:
|
|
445
|
+
return list(self._goals.values())
|
|
446
|
+
return [g for g in self._goals.values() if g.status == status]
|
|
447
|
+
|
|
448
|
+
def get_goal(self, goal_id: str) -> Optional[AgentGoal]:
|
|
449
|
+
"""Get a specific goal by ID."""
|
|
450
|
+
return self._goals.get(goal_id)
|
|
451
|
+
|
|
452
|
+
def update_goal_status(
|
|
453
|
+
self,
|
|
454
|
+
goal_id: str,
|
|
455
|
+
status: GoalStatus,
|
|
456
|
+
progress: Optional[float] = None,
|
|
457
|
+
) -> None:
|
|
458
|
+
"""
|
|
459
|
+
Update goal status.
|
|
460
|
+
|
|
461
|
+
Args:
|
|
462
|
+
goal_id: Goal ID
|
|
463
|
+
status: New status
|
|
464
|
+
progress: Optional progress percentage
|
|
465
|
+
"""
|
|
466
|
+
if goal_id not in self._goals:
|
|
467
|
+
logger.warning(f"Goal {goal_id} not found for agent {self.agent_id}")
|
|
468
|
+
return
|
|
469
|
+
|
|
470
|
+
goal = self._goals[goal_id]
|
|
471
|
+
goal.status = status
|
|
472
|
+
|
|
473
|
+
if progress is not None:
|
|
474
|
+
goal.progress = progress
|
|
475
|
+
|
|
476
|
+
if status == GoalStatus.IN_PROGRESS and goal.started_at is None:
|
|
477
|
+
goal.started_at = datetime.utcnow()
|
|
478
|
+
elif status == GoalStatus.ACHIEVED:
|
|
479
|
+
goal.achieved_at = datetime.utcnow()
|
|
480
|
+
|
|
481
|
+
logger.info(f"Agent {self.agent_id} updated goal {goal_id}: {status.value}")
|
|
482
|
+
|
|
483
|
+
# ==================== Configuration Management ====================
|
|
484
|
+
|
|
485
|
+
def get_config(self) -> AgentConfiguration:
|
|
486
|
+
"""Get agent configuration."""
|
|
487
|
+
return self._config
|
|
488
|
+
|
|
489
|
+
def update_config(self, updates: Dict[str, Any]) -> None:
|
|
490
|
+
"""
|
|
491
|
+
Update agent configuration.
|
|
492
|
+
|
|
493
|
+
Args:
|
|
494
|
+
updates: Configuration updates
|
|
495
|
+
|
|
496
|
+
Raises:
|
|
497
|
+
ConfigurationError: If configuration is invalid
|
|
498
|
+
"""
|
|
499
|
+
try:
|
|
500
|
+
# Update configuration
|
|
501
|
+
for key, value in updates.items():
|
|
502
|
+
if hasattr(self._config, key):
|
|
503
|
+
setattr(self._config, key, value)
|
|
504
|
+
else:
|
|
505
|
+
logger.warning(f"Unknown config key: {key}")
|
|
506
|
+
|
|
507
|
+
self.updated_at = datetime.utcnow()
|
|
508
|
+
logger.info(f"Agent {self.agent_id} configuration updated")
|
|
509
|
+
|
|
510
|
+
except Exception as e:
|
|
511
|
+
raise ConfigurationError(
|
|
512
|
+
f"Failed to update configuration: {str(e)}",
|
|
513
|
+
agent_id=self.agent_id,
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
# ==================== Capability Management ====================
|
|
517
|
+
|
|
518
|
+
def declare_capability(
|
|
519
|
+
self,
|
|
520
|
+
capability_type: str,
|
|
521
|
+
level: str,
|
|
522
|
+
description: Optional[str] = None,
|
|
523
|
+
constraints: Optional[Dict[str, Any]] = None,
|
|
524
|
+
) -> None:
|
|
525
|
+
"""
|
|
526
|
+
Declare an agent capability.
|
|
527
|
+
|
|
528
|
+
Args:
|
|
529
|
+
capability_type: Type of capability
|
|
530
|
+
level: Proficiency level
|
|
531
|
+
description: Capability description
|
|
532
|
+
constraints: Capability constraints
|
|
533
|
+
"""
|
|
534
|
+
from .models import CapabilityLevel
|
|
535
|
+
|
|
536
|
+
capability = AgentCapabilityDeclaration(
|
|
537
|
+
capability_type=capability_type,
|
|
538
|
+
level=CapabilityLevel(level),
|
|
539
|
+
description=description,
|
|
540
|
+
constraints=constraints or {},
|
|
541
|
+
)
|
|
542
|
+
self._capabilities[capability_type] = capability
|
|
543
|
+
logger.info(f"Agent {self.agent_id} declared capability: {capability_type} ({level})")
|
|
544
|
+
|
|
545
|
+
def has_capability(self, capability_type: str) -> bool:
|
|
546
|
+
"""Check if agent has a capability."""
|
|
547
|
+
return capability_type in self._capabilities
|
|
548
|
+
|
|
549
|
+
def get_capabilities(self) -> List[AgentCapabilityDeclaration]:
|
|
550
|
+
"""Get all agent capabilities."""
|
|
551
|
+
return list(self._capabilities.values())
|
|
552
|
+
|
|
553
|
+
# ==================== Metrics Tracking ====================
|
|
554
|
+
|
|
555
|
+
def get_metrics(self) -> AgentMetrics:
|
|
556
|
+
"""Get agent metrics."""
|
|
557
|
+
return self._metrics
|
|
558
|
+
|
|
559
|
+
def update_metrics(
|
|
560
|
+
self,
|
|
561
|
+
execution_time: Optional[float] = None,
|
|
562
|
+
success: bool = True,
|
|
563
|
+
quality_score: Optional[float] = None,
|
|
564
|
+
tokens_used: Optional[int] = None,
|
|
565
|
+
tool_calls: Optional[int] = None,
|
|
566
|
+
) -> None:
|
|
567
|
+
"""
|
|
568
|
+
Update agent metrics.
|
|
569
|
+
|
|
570
|
+
Args:
|
|
571
|
+
execution_time: Task execution time
|
|
572
|
+
success: Whether task succeeded
|
|
573
|
+
quality_score: Quality score (0-1)
|
|
574
|
+
tokens_used: Tokens used
|
|
575
|
+
tool_calls: Number of tool calls
|
|
576
|
+
"""
|
|
577
|
+
self._metrics.total_tasks_executed += 1
|
|
578
|
+
|
|
579
|
+
if success:
|
|
580
|
+
self._metrics.successful_tasks += 1
|
|
581
|
+
else:
|
|
582
|
+
self._metrics.failed_tasks += 1
|
|
583
|
+
|
|
584
|
+
# Update success rate
|
|
585
|
+
self._metrics.success_rate = (
|
|
586
|
+
self._metrics.successful_tasks / self._metrics.total_tasks_executed * 100
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
# Update execution time
|
|
590
|
+
if execution_time is not None:
|
|
591
|
+
self._metrics.total_execution_time += execution_time
|
|
592
|
+
self._metrics.average_execution_time = (
|
|
593
|
+
self._metrics.total_execution_time / self._metrics.total_tasks_executed
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
if self._metrics.min_execution_time is None or execution_time < self._metrics.min_execution_time:
|
|
597
|
+
self._metrics.min_execution_time = execution_time
|
|
598
|
+
if self._metrics.max_execution_time is None or execution_time > self._metrics.max_execution_time:
|
|
599
|
+
self._metrics.max_execution_time = execution_time
|
|
600
|
+
|
|
601
|
+
# Update quality score
|
|
602
|
+
if quality_score is not None:
|
|
603
|
+
if self._metrics.average_quality_score is None:
|
|
604
|
+
self._metrics.average_quality_score = quality_score
|
|
605
|
+
else:
|
|
606
|
+
# Running average
|
|
607
|
+
total_quality = self._metrics.average_quality_score * (
|
|
608
|
+
self._metrics.total_tasks_executed - 1
|
|
609
|
+
)
|
|
610
|
+
self._metrics.average_quality_score = (total_quality + quality_score) / self._metrics.total_tasks_executed
|
|
611
|
+
|
|
612
|
+
# Update resource usage
|
|
613
|
+
if tokens_used is not None:
|
|
614
|
+
self._metrics.total_tokens_used += tokens_used
|
|
615
|
+
if tool_calls is not None:
|
|
616
|
+
self._metrics.total_tool_calls += tool_calls
|
|
617
|
+
|
|
618
|
+
self._metrics.updated_at = datetime.utcnow()
|
|
619
|
+
|
|
620
|
+
# ==================== Serialization ====================
|
|
621
|
+
|
|
622
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
623
|
+
"""
|
|
624
|
+
Serialize agent to dictionary.
|
|
625
|
+
|
|
626
|
+
Returns:
|
|
627
|
+
Dictionary representation
|
|
628
|
+
|
|
629
|
+
Raises:
|
|
630
|
+
SerializationError: If serialization fails
|
|
631
|
+
"""
|
|
632
|
+
try:
|
|
633
|
+
return {
|
|
634
|
+
"agent_id": self.agent_id,
|
|
635
|
+
"name": self.name,
|
|
636
|
+
"agent_type": self.agent_type.value,
|
|
637
|
+
"description": self.description,
|
|
638
|
+
"version": self.version,
|
|
639
|
+
"state": self._state.value,
|
|
640
|
+
"config": self._config.model_dump(),
|
|
641
|
+
"goals": [g.model_dump() for g in self._goals.values()],
|
|
642
|
+
"capabilities": [c.model_dump() for c in self._capabilities.values()],
|
|
643
|
+
"metrics": self._metrics.model_dump(),
|
|
644
|
+
"memory_summary": self.get_memory_summary(),
|
|
645
|
+
"created_at": self.created_at.isoformat(),
|
|
646
|
+
"updated_at": self.updated_at.isoformat(),
|
|
647
|
+
"last_active_at": self.last_active_at.isoformat() if self.last_active_at else None,
|
|
648
|
+
}
|
|
649
|
+
except Exception as e:
|
|
650
|
+
raise SerializationError(
|
|
651
|
+
f"Failed to serialize agent: {str(e)}",
|
|
652
|
+
agent_id=self.agent_id,
|
|
653
|
+
)
|
|
654
|
+
|
|
655
|
+
@classmethod
|
|
656
|
+
def from_dict(cls, data: Dict[str, Any]) -> "BaseAIAgent":
|
|
657
|
+
"""
|
|
658
|
+
Deserialize agent from dictionary.
|
|
659
|
+
|
|
660
|
+
Args:
|
|
661
|
+
data: Dictionary representation
|
|
662
|
+
|
|
663
|
+
Returns:
|
|
664
|
+
Agent instance
|
|
665
|
+
|
|
666
|
+
Raises:
|
|
667
|
+
SerializationError: If deserialization fails
|
|
668
|
+
"""
|
|
669
|
+
raise NotImplementedError("from_dict must be implemented by subclasses")
|
|
670
|
+
|
|
671
|
+
# ==================== Utility Methods ====================
|
|
672
|
+
|
|
673
|
+
def is_available(self) -> bool:
|
|
674
|
+
"""Check if agent is available for tasks."""
|
|
675
|
+
return self._state == AgentState.ACTIVE
|
|
676
|
+
|
|
677
|
+
def is_busy(self) -> bool:
|
|
678
|
+
"""Check if agent is currently busy."""
|
|
679
|
+
return self._state == AgentState.BUSY
|
|
680
|
+
|
|
681
|
+
def __str__(self) -> str:
|
|
682
|
+
"""String representation."""
|
|
683
|
+
return f"Agent({self.agent_id}, {self.name}, {self.agent_type.value}, {self._state.value})"
|
|
684
|
+
|
|
685
|
+
def __repr__(self) -> str:
|
|
686
|
+
"""Detailed representation."""
|
|
687
|
+
return (
|
|
688
|
+
f"BaseAIAgent(agent_id='{self.agent_id}', name='{self.name}', "
|
|
689
|
+
f"type='{self.agent_type.value}', state='{self._state.value}')"
|
|
690
|
+
)
|
|
691
|
+
|