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.

Files changed (36) hide show
  1. aiecs/__init__.py +1 -1
  2. aiecs/domain/__init__.py +120 -0
  3. aiecs/domain/agent/__init__.py +184 -0
  4. aiecs/domain/agent/base_agent.py +691 -0
  5. aiecs/domain/agent/exceptions.py +99 -0
  6. aiecs/domain/agent/hybrid_agent.py +495 -0
  7. aiecs/domain/agent/integration/__init__.py +23 -0
  8. aiecs/domain/agent/integration/context_compressor.py +219 -0
  9. aiecs/domain/agent/integration/context_engine_adapter.py +258 -0
  10. aiecs/domain/agent/integration/retry_policy.py +228 -0
  11. aiecs/domain/agent/integration/role_config.py +217 -0
  12. aiecs/domain/agent/lifecycle.py +298 -0
  13. aiecs/domain/agent/llm_agent.py +309 -0
  14. aiecs/domain/agent/memory/__init__.py +13 -0
  15. aiecs/domain/agent/memory/conversation.py +216 -0
  16. aiecs/domain/agent/migration/__init__.py +15 -0
  17. aiecs/domain/agent/migration/conversion.py +171 -0
  18. aiecs/domain/agent/migration/legacy_wrapper.py +97 -0
  19. aiecs/domain/agent/models.py +263 -0
  20. aiecs/domain/agent/observability.py +443 -0
  21. aiecs/domain/agent/persistence.py +287 -0
  22. aiecs/domain/agent/prompts/__init__.py +25 -0
  23. aiecs/domain/agent/prompts/builder.py +164 -0
  24. aiecs/domain/agent/prompts/formatters.py +192 -0
  25. aiecs/domain/agent/prompts/template.py +264 -0
  26. aiecs/domain/agent/registry.py +261 -0
  27. aiecs/domain/agent/tool_agent.py +267 -0
  28. aiecs/domain/agent/tools/__init__.py +13 -0
  29. aiecs/domain/agent/tools/schema_generator.py +222 -0
  30. aiecs/main.py +2 -2
  31. {aiecs-1.3.8.dist-info → aiecs-1.4.0.dist-info}/METADATA +1 -1
  32. {aiecs-1.3.8.dist-info → aiecs-1.4.0.dist-info}/RECORD +36 -9
  33. {aiecs-1.3.8.dist-info → aiecs-1.4.0.dist-info}/WHEEL +0 -0
  34. {aiecs-1.3.8.dist-info → aiecs-1.4.0.dist-info}/entry_points.txt +0 -0
  35. {aiecs-1.3.8.dist-info → aiecs-1.4.0.dist-info}/licenses/LICENSE +0 -0
  36. {aiecs-1.3.8.dist-info → aiecs-1.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,264 @@
1
+ """
2
+ Prompt Templates
3
+
4
+ Native template system with variable substitution.
5
+ """
6
+
7
+ import re
8
+ from typing import Dict, Any, List, Optional
9
+ from dataclasses import dataclass
10
+
11
+ from aiecs.llm import LLMMessage
12
+
13
+
14
+ class TemplateMissingVariableError(Exception):
15
+ """Raised when required template variable is missing."""
16
+ pass
17
+
18
+
19
+ class PromptTemplate:
20
+ """
21
+ String-based prompt template with {variable} substitution.
22
+
23
+ Example:
24
+ template = PromptTemplate(
25
+ "Hello {name}, you are a {role}.",
26
+ required_variables=["name", "role"]
27
+ )
28
+ result = template.format(name="Alice", role="developer")
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ template: str,
34
+ required_variables: Optional[List[str]] = None,
35
+ defaults: Optional[Dict[str, str]] = None,
36
+ ):
37
+ """
38
+ Initialize prompt template.
39
+
40
+ Args:
41
+ template: Template string with {variable} placeholders
42
+ required_variables: List of required variable names
43
+ defaults: Default values for optional variables
44
+ """
45
+ self.template = template
46
+ self.required_variables = required_variables or []
47
+ self.defaults = defaults or {}
48
+
49
+ # Extract all variables from template
50
+ self._extract_variables()
51
+
52
+ def _extract_variables(self) -> None:
53
+ """Extract variable names from template."""
54
+ # Find all {variable_name} patterns
55
+ pattern = r'\{(\w+)\}'
56
+ self.variables = set(re.findall(pattern, self.template))
57
+
58
+ def format(self, **kwargs) -> str:
59
+ """
60
+ Format template with provided variables.
61
+
62
+ Args:
63
+ **kwargs: Variable values
64
+
65
+ Returns:
66
+ Formatted string
67
+
68
+ Raises:
69
+ TemplateMissingVariableError: If required variable missing
70
+ """
71
+ # Check required variables
72
+ for var in self.required_variables:
73
+ if var not in kwargs and var not in self.defaults:
74
+ raise TemplateMissingVariableError(
75
+ f"Required variable '{var}' not provided"
76
+ )
77
+
78
+ # Merge with defaults
79
+ values = {**self.defaults, **kwargs}
80
+
81
+ # Format template
82
+ try:
83
+ return self.template.format(**values)
84
+ except KeyError as e:
85
+ raise TemplateMissingVariableError(
86
+ f"Variable {e} not provided and has no default"
87
+ )
88
+
89
+ def partial(self, **kwargs) -> "PromptTemplate":
90
+ """
91
+ Create a partial template with some variables pre-filled.
92
+
93
+ Args:
94
+ **kwargs: Variable values to pre-fill
95
+
96
+ Returns:
97
+ New PromptTemplate with updated defaults
98
+ """
99
+ new_defaults = {**self.defaults, **kwargs}
100
+ return PromptTemplate(
101
+ template=self.template,
102
+ required_variables=self.required_variables,
103
+ defaults=new_defaults,
104
+ )
105
+
106
+ def __repr__(self) -> str:
107
+ return f"PromptTemplate(variables={self.variables})"
108
+
109
+
110
+ @dataclass
111
+ class MessageTemplate:
112
+ """Template for a single message."""
113
+ role: str
114
+ content: str
115
+
116
+
117
+ class ChatPromptTemplate:
118
+ """
119
+ Multi-message chat template.
120
+
121
+ Example:
122
+ template = ChatPromptTemplate([
123
+ MessageTemplate("system", "You are a {role}."),
124
+ MessageTemplate("user", "{task}"),
125
+ ])
126
+ messages = template.format_messages(role="assistant", task="Help me")
127
+ """
128
+
129
+ def __init__(
130
+ self,
131
+ messages: List[MessageTemplate],
132
+ required_variables: Optional[List[str]] = None,
133
+ defaults: Optional[Dict[str, str]] = None,
134
+ ):
135
+ """
136
+ Initialize chat template.
137
+
138
+ Args:
139
+ messages: List of message templates
140
+ required_variables: List of required variable names
141
+ defaults: Default values for optional variables
142
+ """
143
+ self.messages = messages
144
+ self.required_variables = required_variables or []
145
+ self.defaults = defaults or {}
146
+
147
+ # Extract all variables
148
+ self._extract_variables()
149
+
150
+ def _extract_variables(self) -> None:
151
+ """Extract variables from all message templates."""
152
+ self.variables = set()
153
+ pattern = r'\{(\w+)\}'
154
+
155
+ for msg in self.messages:
156
+ vars_in_msg = set(re.findall(pattern, msg.content))
157
+ self.variables.update(vars_in_msg)
158
+
159
+ def format_messages(self, **kwargs) -> List[LLMMessage]:
160
+ """
161
+ Format all messages with provided variables.
162
+
163
+ Args:
164
+ **kwargs: Variable values
165
+
166
+ Returns:
167
+ List of LLMMessage instances
168
+
169
+ Raises:
170
+ TemplateMissingVariableError: If required variable missing
171
+ """
172
+ # Check required variables
173
+ for var in self.required_variables:
174
+ if var not in kwargs and var not in self.defaults:
175
+ raise TemplateMissingVariableError(
176
+ f"Required variable '{var}' not provided"
177
+ )
178
+
179
+ # Merge with defaults
180
+ values = {**self.defaults, **kwargs}
181
+
182
+ # Format each message
183
+ formatted_messages = []
184
+ for msg_template in self.messages:
185
+ try:
186
+ content = msg_template.content.format(**values)
187
+ formatted_messages.append(
188
+ LLMMessage(role=msg_template.role, content=content)
189
+ )
190
+ except KeyError as e:
191
+ raise TemplateMissingVariableError(
192
+ f"Variable {e} not provided and has no default"
193
+ )
194
+
195
+ return formatted_messages
196
+
197
+ def partial(self, **kwargs) -> "ChatPromptTemplate":
198
+ """
199
+ Create a partial template with some variables pre-filled.
200
+
201
+ Args:
202
+ **kwargs: Variable values to pre-fill
203
+
204
+ Returns:
205
+ New ChatPromptTemplate with updated defaults
206
+ """
207
+ new_defaults = {**self.defaults, **kwargs}
208
+ return ChatPromptTemplate(
209
+ messages=self.messages,
210
+ required_variables=self.required_variables,
211
+ defaults=new_defaults,
212
+ )
213
+
214
+ def add_message(self, role: str, content: str) -> "ChatPromptTemplate":
215
+ """
216
+ Add a message to the template.
217
+
218
+ Args:
219
+ role: Message role
220
+ content: Message content template
221
+
222
+ Returns:
223
+ New ChatPromptTemplate with added message
224
+ """
225
+ new_messages = self.messages + [MessageTemplate(role, content)]
226
+ return ChatPromptTemplate(
227
+ messages=new_messages,
228
+ required_variables=self.required_variables,
229
+ defaults=self.defaults,
230
+ )
231
+
232
+ def __repr__(self) -> str:
233
+ return f"ChatPromptTemplate(messages={len(self.messages)}, variables={self.variables})"
234
+
235
+
236
+ def create_system_prompt(content: str) -> ChatPromptTemplate:
237
+ """
238
+ Helper to create a chat template with system message.
239
+
240
+ Args:
241
+ content: System message content
242
+
243
+ Returns:
244
+ ChatPromptTemplate with system message
245
+ """
246
+ return ChatPromptTemplate([MessageTemplate("system", content)])
247
+
248
+
249
+ def create_basic_chat(system: str, user: str) -> ChatPromptTemplate:
250
+ """
251
+ Helper to create a basic system + user chat template.
252
+
253
+ Args:
254
+ system: System message content
255
+ user: User message content
256
+
257
+ Returns:
258
+ ChatPromptTemplate with system and user messages
259
+ """
260
+ return ChatPromptTemplate([
261
+ MessageTemplate("system", system),
262
+ MessageTemplate("user", user),
263
+ ])
264
+
@@ -0,0 +1,261 @@
1
+ """
2
+ Agent Registry
3
+
4
+ Central registry for tracking and managing active agents.
5
+ """
6
+
7
+ import logging
8
+ from typing import Dict, List, Optional, Set
9
+ from datetime import datetime
10
+
11
+ from .base_agent import BaseAIAgent
12
+ from .models import AgentState, AgentType
13
+ from .exceptions import AgentNotFoundError, AgentAlreadyRegisteredError
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class AgentRegistry:
19
+ """
20
+ Central registry for tracking and managing active agents.
21
+
22
+ Thread-safe registry for agent lifecycle management.
23
+ """
24
+
25
+ def __init__(self):
26
+ """Initialize agent registry."""
27
+ self._agents: Dict[str, BaseAIAgent] = {}
28
+ self._agents_by_type: Dict[AgentType, Set[str]] = {}
29
+ self._agents_by_state: Dict[AgentState, Set[str]] = {}
30
+
31
+ logger.info("AgentRegistry initialized")
32
+
33
+ def register(self, agent: BaseAIAgent) -> None:
34
+ """
35
+ Register an agent.
36
+
37
+ Args:
38
+ agent: Agent to register
39
+
40
+ Raises:
41
+ AgentAlreadyRegisteredError: If agent already registered
42
+ """
43
+ if agent.agent_id in self._agents:
44
+ raise AgentAlreadyRegisteredError(agent.agent_id)
45
+
46
+ # Register agent
47
+ self._agents[agent.agent_id] = agent
48
+
49
+ # Index by type
50
+ if agent.agent_type not in self._agents_by_type:
51
+ self._agents_by_type[agent.agent_type] = set()
52
+ self._agents_by_type[agent.agent_type].add(agent.agent_id)
53
+
54
+ # Index by state
55
+ if agent.state not in self._agents_by_state:
56
+ self._agents_by_state[agent.state] = set()
57
+ self._agents_by_state[agent.state].add(agent.agent_id)
58
+
59
+ logger.info(f"Agent registered: {agent.agent_id} ({agent.agent_type.value})")
60
+
61
+ def unregister(self, agent_id: str) -> None:
62
+ """
63
+ Unregister an agent.
64
+
65
+ Args:
66
+ agent_id: Agent identifier
67
+
68
+ Raises:
69
+ AgentNotFoundError: If agent not found
70
+ """
71
+ agent = self.get(agent_id)
72
+
73
+ # Remove from indexes
74
+ if agent.agent_type in self._agents_by_type:
75
+ self._agents_by_type[agent.agent_type].discard(agent_id)
76
+
77
+ if agent.state in self._agents_by_state:
78
+ self._agents_by_state[agent.state].discard(agent_id)
79
+
80
+ # Remove from main registry
81
+ del self._agents[agent_id]
82
+
83
+ logger.info(f"Agent unregistered: {agent_id}")
84
+
85
+ def get(self, agent_id: str) -> BaseAIAgent:
86
+ """
87
+ Get agent by ID.
88
+
89
+ Args:
90
+ agent_id: Agent identifier
91
+
92
+ Returns:
93
+ Agent instance
94
+
95
+ Raises:
96
+ AgentNotFoundError: If agent not found
97
+ """
98
+ if agent_id not in self._agents:
99
+ raise AgentNotFoundError(agent_id)
100
+
101
+ return self._agents[agent_id]
102
+
103
+ def get_optional(self, agent_id: str) -> Optional[BaseAIAgent]:
104
+ """
105
+ Get agent by ID, returning None if not found.
106
+
107
+ Args:
108
+ agent_id: Agent identifier
109
+
110
+ Returns:
111
+ Agent instance or None
112
+ """
113
+ return self._agents.get(agent_id)
114
+
115
+ def exists(self, agent_id: str) -> bool:
116
+ """
117
+ Check if agent exists.
118
+
119
+ Args:
120
+ agent_id: Agent identifier
121
+
122
+ Returns:
123
+ True if agent exists
124
+ """
125
+ return agent_id in self._agents
126
+
127
+ def list_all(self) -> List[BaseAIAgent]:
128
+ """
129
+ List all registered agents.
130
+
131
+ Returns:
132
+ List of all agents
133
+ """
134
+ return list(self._agents.values())
135
+
136
+ def list_by_type(self, agent_type: AgentType) -> List[BaseAIAgent]:
137
+ """
138
+ List agents by type.
139
+
140
+ Args:
141
+ agent_type: Agent type
142
+
143
+ Returns:
144
+ List of agents of specified type
145
+ """
146
+ agent_ids = self._agents_by_type.get(agent_type, set())
147
+ return [self._agents[aid] for aid in agent_ids if aid in self._agents]
148
+
149
+ def list_by_state(self, state: AgentState) -> List[BaseAIAgent]:
150
+ """
151
+ List agents by state.
152
+
153
+ Args:
154
+ state: Agent state
155
+
156
+ Returns:
157
+ List of agents in specified state
158
+ """
159
+ agent_ids = self._agents_by_state.get(state, set())
160
+ return [self._agents[aid] for aid in agent_ids if aid in self._agents]
161
+
162
+ def update_state_index(self, agent_id: str, old_state: AgentState, new_state: AgentState) -> None:
163
+ """
164
+ Update state index when agent state changes.
165
+
166
+ Args:
167
+ agent_id: Agent identifier
168
+ old_state: Previous state
169
+ new_state: New state
170
+ """
171
+ # Remove from old state index
172
+ if old_state in self._agents_by_state:
173
+ self._agents_by_state[old_state].discard(agent_id)
174
+
175
+ # Add to new state index
176
+ if new_state not in self._agents_by_state:
177
+ self._agents_by_state[new_state] = set()
178
+ self._agents_by_state[new_state].add(agent_id)
179
+
180
+ def count(self) -> int:
181
+ """
182
+ Get total number of registered agents.
183
+
184
+ Returns:
185
+ Number of agents
186
+ """
187
+ return len(self._agents)
188
+
189
+ def count_by_type(self, agent_type: AgentType) -> int:
190
+ """
191
+ Get count of agents by type.
192
+
193
+ Args:
194
+ agent_type: Agent type
195
+
196
+ Returns:
197
+ Number of agents of specified type
198
+ """
199
+ return len(self._agents_by_type.get(agent_type, set()))
200
+
201
+ def count_by_state(self, state: AgentState) -> int:
202
+ """
203
+ Get count of agents by state.
204
+
205
+ Args:
206
+ state: Agent state
207
+
208
+ Returns:
209
+ Number of agents in specified state
210
+ """
211
+ return len(self._agents_by_state.get(state, set()))
212
+
213
+ def get_stats(self) -> Dict:
214
+ """
215
+ Get registry statistics.
216
+
217
+ Returns:
218
+ Dictionary with stats
219
+ """
220
+ return {
221
+ "total_agents": self.count(),
222
+ "by_type": {
223
+ agent_type.value: len(agent_ids)
224
+ for agent_type, agent_ids in self._agents_by_type.items()
225
+ },
226
+ "by_state": {
227
+ state.value: len(agent_ids)
228
+ for state, agent_ids in self._agents_by_state.items()
229
+ },
230
+ }
231
+
232
+ def clear(self) -> None:
233
+ """Clear all agents from registry."""
234
+ self._agents.clear()
235
+ self._agents_by_type.clear()
236
+ self._agents_by_state.clear()
237
+ logger.info("AgentRegistry cleared")
238
+
239
+
240
+ # Global registry instance
241
+ _global_registry: Optional[AgentRegistry] = None
242
+
243
+
244
+ def get_global_registry() -> AgentRegistry:
245
+ """
246
+ Get or create global agent registry.
247
+
248
+ Returns:
249
+ Global AgentRegistry instance
250
+ """
251
+ global _global_registry
252
+ if _global_registry is None:
253
+ _global_registry = AgentRegistry()
254
+ return _global_registry
255
+
256
+
257
+ def reset_global_registry() -> None:
258
+ """Reset global registry (primarily for testing)."""
259
+ global _global_registry
260
+ _global_registry = None
261
+