acgp 0.1.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.
acgp/__init__.py ADDED
@@ -0,0 +1,75 @@
1
+ """
2
+ ACGP SDK - Agentic Cognitive Governance Protocol
3
+ Lightweight Python SDK for AI agent oversight integration.
4
+
5
+ Basic Usage (Real-time Feedback - Recommended):
6
+
7
+ from acgp import ACGPClient
8
+
9
+ def handle_feedback(trace_id: str, feedback_data: dict):
10
+ print(f"Feedback for {trace_id}: {feedback_data}")
11
+
12
+ client = ACGPClient(
13
+ api_key="sk_...",
14
+ on_feedback=handle_feedback # Real-time SSE streaming
15
+ )
16
+
17
+ with client.trace(agent_type="planner", goal="Analyze query") as trace:
18
+ result = my_agent.run(query)
19
+ trace.set_output(result)
20
+ # Feedback callback fires automatically!
21
+
22
+ client.wait_for_feedback(timeout=30) # Optional: wait before shutdown
23
+ client.close()
24
+
25
+
26
+ Alternative Usage (Manual Polling):
27
+
28
+ from acgp import ACGPClient, acgp_trace
29
+
30
+ client = ACGPClient(api_key="sk_...")
31
+
32
+ # Option 1: Decorator
33
+ @acgp_trace(client, agent_type="planner")
34
+ def my_agent(query):
35
+ return process(query)
36
+
37
+ # Option 2: Context manager
38
+ with client.trace(agent_type="executor", goal="Process query") as trace:
39
+ result = my_agent.run(query)
40
+ trace.set_output(result)
41
+
42
+ # Option 3: Manual capture
43
+ trace_id = client.capture_trace(
44
+ agent_type="planner",
45
+ goal="Analyze request",
46
+ final_output="Result..."
47
+ )
48
+
49
+ # Flush and poll for results
50
+ client.flush()
51
+ score = client.get_score(trace_id)
52
+ feedback = client.get_feedback(trace_id)
53
+ """
54
+
55
+ __version__ = "0.1.0"
56
+
57
+ from .client import ACGPClient, acgp_trace, TraceContext, FeedbackCallback
58
+ from .config import ACGPConfig, load_config
59
+ from .transport import BatchResult, QueueConfig, BatchConfig, RetryConfig, SSEConfig
60
+
61
+ __all__ = [
62
+ "__version__",
63
+ "ACGPClient",
64
+ "acgp_trace",
65
+ "TraceContext",
66
+ "FeedbackCallback",
67
+ "ACGPConfig",
68
+ "load_config",
69
+ # Transport (for advanced users)
70
+ "BatchResult",
71
+ "QueueConfig",
72
+ "BatchConfig",
73
+ "RetryConfig",
74
+ "SSEConfig",
75
+ ]
@@ -0,0 +1,13 @@
1
+ """
2
+ CGP SDK Adapters
3
+ Framework-specific adapters for agent integration.
4
+ """
5
+
6
+ from .generic import GenericAdapter
7
+
8
+ # CrewAI adapter is optional - only import if crewai is installed
9
+ try:
10
+ from .crewai import CrewAIAdapter
11
+ __all__ = ["GenericAdapter", "CrewAIAdapter"]
12
+ except ImportError:
13
+ __all__ = ["GenericAdapter"]
@@ -0,0 +1,400 @@
1
+ """
2
+ CrewAI Adapter - Framework-Specific Adapter for CrewAI Agents
3
+
4
+ This adapter enables Steward Agent to monitor CrewAI agents transparently.
5
+ It handles CrewAI-specific output formats, agent structures, and feedback injection.
6
+
7
+ Supports:
8
+ - CrewAI Agent objects
9
+ - CrewAI Crew objects
10
+ - CrewAI Task objects
11
+ - CrewOutput parsing
12
+ - Agent role/goal/backstory extraction
13
+
14
+ Usage Example:
15
+ ```python
16
+ from crewai import Agent
17
+ from src.adapters import CrewAIAdapter
18
+ from src.wrapper import StewardAgentWrapper
19
+ from src.core import StewardAgent
20
+
21
+ # Create CrewAI agent
22
+ agent = Agent(
23
+ role="Researcher",
24
+ goal="Find latest AI developments",
25
+ backstory="Expert AI researcher"
26
+ )
27
+
28
+ # Wrap with Steward Agent monitoring
29
+ adapter = CrewAIAdapter()
30
+ steward = StewardAgent()
31
+ wrapped = StewardAgentWrapper(agent, adapter, steward)
32
+
33
+ # Use normally - monitored automatically
34
+ result = wrapped.execute("Research AI safety")
35
+ ```
36
+
37
+ Framework Dependency:
38
+ This adapter imports CrewAI but handles ImportError gracefully.
39
+ If CrewAI is not installed, adapter will report it doesn't support agents.
40
+ """
41
+
42
+ import logging
43
+ from typing import Any, Dict, Optional, Union
44
+
45
+ # Try to import CrewAI
46
+ try:
47
+ from crewai import Agent, Task, Crew
48
+ from crewai.agent import Agent as CrewAIAgent
49
+ CREWAI_AVAILABLE = True
50
+ except ImportError:
51
+ CREWAI_AVAILABLE = False
52
+ # Placeholders for type hints
53
+ class Agent: pass
54
+ class Task: pass
55
+ class Crew: pass
56
+ class CrewAIAgent: pass
57
+
58
+ try:
59
+ from ..wrapper.agent_protocol import (
60
+ AgentAdapter,
61
+ AgentExecutionContext,
62
+ AgentExecutionResult
63
+ )
64
+ from ..utils.data_models import (
65
+ AgentReasoningTrace,
66
+ SocraticNudge,
67
+ HITLAlert
68
+ )
69
+ except ImportError:
70
+ from wrapper.agent_protocol import (
71
+ AgentAdapter,
72
+ AgentExecutionContext,
73
+ AgentExecutionResult
74
+ )
75
+ from utils.data_models import (
76
+ AgentReasoningTrace,
77
+ SocraticNudge,
78
+ HITLAlert
79
+ )
80
+
81
+
82
+ class CrewAIAdapter(AgentAdapter):
83
+ """
84
+ Adapter for CrewAI agents, crews, and tasks.
85
+
86
+ This adapter translates between CrewAI's agent framework and Steward Agent's
87
+ universal monitoring interface.
88
+
89
+ CrewAI-Specific Handling:
90
+ - Extracts reasoning from CrewOutput objects
91
+ - Handles .raw and .content attributes
92
+ - Injects feedback into agent context
93
+ - Extracts role, goal, backstory from agents
94
+
95
+ Attributes:
96
+ logger: Logger instance
97
+ """
98
+
99
+ def __init__(self, logger: Optional[logging.Logger] = None):
100
+ """
101
+ Initialize CrewAI Adapter.
102
+
103
+ Args:
104
+ logger: Optional logger instance
105
+ """
106
+ self.logger = logger or logging.getLogger(__name__)
107
+
108
+ if not CREWAI_AVAILABLE:
109
+ self.logger.warning(
110
+ "CrewAI is not installed. CrewAIAdapter will not support any agents. "
111
+ "Install with: pip install crewai"
112
+ )
113
+ else:
114
+ self.logger.debug("CrewAIAdapter initialized with CrewAI support")
115
+
116
+ def supports_agent(self, agent: Any) -> bool:
117
+ """
118
+ Check if agent is a CrewAI Agent, Crew, or Task.
119
+
120
+ Args:
121
+ agent: Object to check
122
+
123
+ Returns:
124
+ True if agent is from CrewAI framework
125
+ """
126
+ if not CREWAI_AVAILABLE:
127
+ return False
128
+
129
+ is_supported = isinstance(agent, (Agent, CrewAIAgent, Crew, Task))
130
+
131
+ if not is_supported:
132
+ self.logger.debug(
133
+ f"Agent type {type(agent).__name__} is not a CrewAI agent"
134
+ )
135
+
136
+ return is_supported
137
+
138
+ def get_execution_methods(self) -> list:
139
+ """
140
+ Return list of method names that trigger CrewAI agent execution.
141
+
142
+ CrewAI agents use:
143
+ - execute(): Generic execution method
144
+ - kickoff(): Main CrewAI execution method
145
+
146
+ Returns:
147
+ List of method names to monitor: ['execute', 'kickoff']
148
+ """
149
+ return ['execute', 'kickoff']
150
+
151
+ def extract_reasoning(
152
+ self,
153
+ agent: Any,
154
+ execution_result: Any,
155
+ execution_context: Optional[AgentExecutionContext] = None
156
+ ) -> AgentReasoningTrace:
157
+ """
158
+ Extract reasoning trace from CrewAI execution result.
159
+
160
+ Handles CrewAI-specific output formats:
161
+ - CrewOutput objects (has .raw, .content)
162
+ - TaskOutput objects
163
+ - String outputs
164
+ - Dict outputs
165
+
166
+ Args:
167
+ agent: CrewAI agent that executed
168
+ execution_result: Result from agent execution
169
+ execution_context: Optional execution context
170
+
171
+ Returns:
172
+ AgentReasoningTrace with extracted information
173
+ """
174
+ # Extract agent name and type
175
+ agent_name = self._get_agent_name(agent)
176
+ agent_type = self._get_agent_type(agent)
177
+
178
+ # Extract reasoning and output from CrewAI result
179
+ reasoning_text, final_output = self._extract_from_crew_output(execution_result)
180
+
181
+ # Get goal
182
+ goal = ""
183
+ if execution_context:
184
+ goal = execution_context.goal or execution_context.task or ""
185
+ elif hasattr(agent, 'goal'):
186
+ goal = str(agent.goal)
187
+
188
+ # Create reasoning trace
189
+ trace = AgentReasoningTrace(
190
+ agent_name=agent_name,
191
+ agent_type=agent_type,
192
+ reasoning_text=reasoning_text,
193
+ final_output=final_output,
194
+ goal=goal,
195
+ context_information=self._extract_context_info(agent, execution_context)
196
+ )
197
+
198
+ self.logger.debug(
199
+ f"Extracted CrewAI reasoning trace for {agent_name}: "
200
+ f"output_length={len(str(final_output))}"
201
+ )
202
+
203
+ return trace
204
+
205
+ def inject_feedback(
206
+ self,
207
+ agent: Any,
208
+ feedback: Union[SocraticNudge, HITLAlert, str],
209
+ execution_context: Optional[AgentExecutionContext] = None
210
+ ) -> None:
211
+ """
212
+ Inject Steward Agent feedback into CrewAI agent.
213
+
214
+ Strategy:
215
+ 1. If agent has context attribute, append feedback
216
+ 2. If agent has memory, add to memory
217
+ 3. Otherwise, log warning
218
+
219
+ Args:
220
+ agent: CrewAI agent to inject feedback into
221
+ feedback: Feedback from Steward Agent
222
+ execution_context: Optional execution context
223
+ """
224
+ feedback_text = self._feedback_to_string(feedback)
225
+
226
+ injected = False
227
+
228
+ # Strategy 1: Agent has context
229
+ if hasattr(agent, 'context') and agent.context is not None:
230
+ if isinstance(agent.context, list):
231
+ agent.context.append(feedback_text)
232
+ injected = True
233
+ self.logger.debug("Feedback injected into CrewAI agent.context")
234
+ elif isinstance(agent.context, str):
235
+ agent.context += f"\n\n{feedback_text}"
236
+ injected = True
237
+ self.logger.debug("Feedback appended to CrewAI agent.context string")
238
+
239
+ # Strategy 2: Agent has memory
240
+ if not injected and hasattr(agent, 'memory'):
241
+ try:
242
+ agent.memory.add(feedback_text)
243
+ injected = True
244
+ self.logger.debug("Feedback added to CrewAI agent.memory")
245
+ except Exception as e:
246
+ self.logger.debug(f"Could not add to agent.memory: {e}")
247
+
248
+ # Fallback: Log warning
249
+ if not injected:
250
+ self.logger.warning(
251
+ f"Could not inject feedback into CrewAI agent {agent_name}. "
252
+ f"Agent has no compatible context or memory attribute."
253
+ )
254
+
255
+ def get_agent_metadata(self, agent: Any) -> Dict[str, Any]:
256
+ """
257
+ Extract metadata from CrewAI agent.
258
+
259
+ Extracts:
260
+ - role: Agent's role
261
+ - goal: Agent's goal
262
+ - backstory: Agent's backstory
263
+ - verbose: Verbosity setting
264
+ - allow_delegation: Delegation setting
265
+ - tools: Available tools
266
+
267
+ Args:
268
+ agent: CrewAI agent
269
+
270
+ Returns:
271
+ Dictionary with agent metadata
272
+ """
273
+ metadata = {
274
+ 'framework': 'crewai',
275
+ 'adapter': 'CrewAIAdapter',
276
+ 'agent_name': self._get_agent_name(agent),
277
+ 'agent_type': self._get_agent_type(agent),
278
+ 'agent_class': type(agent).__name__,
279
+ }
280
+
281
+ # Extract CrewAI-specific attributes
282
+ if hasattr(agent, 'role'):
283
+ metadata['role'] = str(agent.role)
284
+ if hasattr(agent, 'goal'):
285
+ metadata['goal'] = str(agent.goal)
286
+ if hasattr(agent, 'backstory'):
287
+ metadata['backstory'] = str(agent.backstory)[:200] # Limit length
288
+ if hasattr(agent, 'verbose'):
289
+ metadata['verbose'] = agent.verbose
290
+ if hasattr(agent, 'allow_delegation'):
291
+ metadata['allow_delegation'] = agent.allow_delegation
292
+ if hasattr(agent, 'tools'):
293
+ metadata['tools_count'] = len(agent.tools) if agent.tools else 0
294
+
295
+ self.logger.debug(f"Extracted metadata for CrewAI agent {metadata['agent_name']}")
296
+
297
+ return metadata
298
+
299
+ # ========================================
300
+ # Helper Methods
301
+ # ========================================
302
+
303
+ def _get_agent_name(self, agent: Any) -> str:
304
+ """Get agent name from CrewAI agent."""
305
+ if hasattr(agent, 'role'):
306
+ return str(agent.role)
307
+ if hasattr(agent, 'name'):
308
+ return str(agent.name)
309
+ return type(agent).__name__
310
+
311
+ def _get_agent_type(self, agent: Any) -> str:
312
+ """Determine agent type from CrewAI agent."""
313
+ # Check if it's a planner or executor based on role/goal
314
+ if hasattr(agent, 'role'):
315
+ role = str(agent.role).lower()
316
+ if 'plan' in role or 'manager' in role or 'coordinator' in role:
317
+ return 'planner'
318
+ return 'executor'
319
+ return 'generic'
320
+
321
+ def _extract_from_crew_output(self, result: Any) -> tuple[str, str]:
322
+ """
323
+ Extract reasoning and output from CrewAI result.
324
+
325
+ Handles different CrewAI output formats.
326
+
327
+ Returns:
328
+ Tuple of (reasoning_text, final_output)
329
+ """
330
+ reasoning_text = ""
331
+ final_output = ""
332
+
333
+ # Case 1: CrewOutput object (has .raw attribute)
334
+ if hasattr(result, 'raw'):
335
+ final_output = str(result.raw)
336
+ reasoning_text = f"CrewAI Output (raw): {final_output}"
337
+
338
+ # Case 2: Has .content attribute
339
+ elif hasattr(result, 'content'):
340
+ final_output = str(result.content)
341
+ reasoning_text = f"CrewAI Output (content): {final_output}"
342
+
343
+ # Case 3: TaskOutput object
344
+ elif hasattr(result, 'output'):
345
+ final_output = str(result.output)
346
+ if hasattr(result, 'description'):
347
+ reasoning_text = f"Task: {result.description}\nOutput: {final_output}"
348
+ else:
349
+ reasoning_text = f"Task Output: {final_output}"
350
+
351
+ # Case 4: Dictionary
352
+ elif isinstance(result, dict):
353
+ final_output = str(result.get('output', result.get('result', str(result))))
354
+ reasoning_text = str(result.get('reasoning', f"CrewAI result: {final_output}"))
355
+
356
+ # Case 5: String or other
357
+ else:
358
+ final_output = str(result)
359
+ reasoning_text = f"CrewAI execution result: {final_output}"
360
+
361
+ return reasoning_text, final_output
362
+
363
+ def _extract_context_info(
364
+ self,
365
+ agent: Any,
366
+ execution_context: Optional[AgentExecutionContext]
367
+ ) -> Dict[str, Any]:
368
+ """Extract context information for reasoning trace."""
369
+ context_info = {
370
+ 'adapter': 'CrewAIAdapter',
371
+ 'framework': 'crewai',
372
+ }
373
+
374
+ if execution_context:
375
+ context_info['execution_context'] = {
376
+ 'agent_id': execution_context.agent_id,
377
+ 'task': execution_context.task,
378
+ }
379
+
380
+ # Add CrewAI-specific context
381
+ if hasattr(agent, 'role'):
382
+ context_info['agent_role'] = str(agent.role)
383
+ if hasattr(agent, 'goal'):
384
+ context_info['agent_goal'] = str(agent.goal)
385
+
386
+ return context_info
387
+
388
+ def _feedback_to_string(self, feedback: Union[SocraticNudge, HITLAlert, str]) -> str:
389
+ """Convert feedback to string representation."""
390
+ if isinstance(feedback, str):
391
+ return feedback
392
+ elif isinstance(feedback, SocraticNudge):
393
+ return f"[Steward Agent - Socratic Nudge]\n{feedback.question}"
394
+ elif isinstance(feedback, HITLAlert):
395
+ return f"[Steward Agent - HITL Alert]\n{feedback.alert_message}\nPriority: {feedback.priority}"
396
+ else:
397
+ return str(feedback)
398
+
399
+
400
+ __all__ = ['CrewAIAdapter']