control-zero 0.2.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.
- control_zero/__init__.py +31 -0
- control_zero/client.py +584 -0
- control_zero/integrations/crewai/__init__.py +53 -0
- control_zero/integrations/crewai/agent.py +267 -0
- control_zero/integrations/crewai/crew.py +381 -0
- control_zero/integrations/crewai/task.py +291 -0
- control_zero/integrations/crewai/tool.py +299 -0
- control_zero/integrations/langchain/__init__.py +58 -0
- control_zero/integrations/langchain/agent.py +311 -0
- control_zero/integrations/langchain/callbacks.py +441 -0
- control_zero/integrations/langchain/chain.py +319 -0
- control_zero/integrations/langchain/graph.py +441 -0
- control_zero/integrations/langchain/tool.py +271 -0
- control_zero/llm/__init__.py +77 -0
- control_zero/llm/anthropic/__init__.py +35 -0
- control_zero/llm/anthropic/client.py +136 -0
- control_zero/llm/anthropic/messages.py +375 -0
- control_zero/llm/base.py +551 -0
- control_zero/llm/cohere/__init__.py +32 -0
- control_zero/llm/cohere/client.py +402 -0
- control_zero/llm/gemini/__init__.py +34 -0
- control_zero/llm/gemini/client.py +486 -0
- control_zero/llm/groq/__init__.py +32 -0
- control_zero/llm/groq/client.py +330 -0
- control_zero/llm/mistral/__init__.py +32 -0
- control_zero/llm/mistral/client.py +319 -0
- control_zero/llm/ollama/__init__.py +31 -0
- control_zero/llm/ollama/client.py +439 -0
- control_zero/llm/openai/__init__.py +34 -0
- control_zero/llm/openai/chat.py +331 -0
- control_zero/llm/openai/client.py +182 -0
- control_zero/logging/__init__.py +5 -0
- control_zero/logging/async_logger.py +65 -0
- control_zero/mcp/__init__.py +5 -0
- control_zero/mcp/middleware.py +148 -0
- control_zero/policy/__init__.py +5 -0
- control_zero/policy/enforcer.py +99 -0
- control_zero/secrets/__init__.py +5 -0
- control_zero/secrets/manager.py +77 -0
- control_zero/types.py +51 -0
- control_zero-0.2.0.dist-info/METADATA +216 -0
- control_zero-0.2.0.dist-info/RECORD +44 -0
- control_zero-0.2.0.dist-info/WHEEL +4 -0
- control_zero-0.2.0.dist-info/licenses/LICENSE +17 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Governed CrewAI Agent wrapper.
|
|
3
|
+
|
|
4
|
+
Provides governance enforcement for CrewAI agents including:
|
|
5
|
+
- Role-based access control
|
|
6
|
+
- Tool filtering based on agent role
|
|
7
|
+
- Action logging
|
|
8
|
+
- Execution tracking
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import time
|
|
14
|
+
from functools import wraps
|
|
15
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from crewai import Agent
|
|
19
|
+
from crewai.tools import BaseTool as CrewAIBaseTool
|
|
20
|
+
CREWAI_AVAILABLE = True
|
|
21
|
+
except ImportError:
|
|
22
|
+
CREWAI_AVAILABLE = False
|
|
23
|
+
class Agent:
|
|
24
|
+
pass
|
|
25
|
+
class CrewAIBaseTool:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
from control_zero.client import ControlZeroClient
|
|
29
|
+
from control_zero.policy import PolicyDecision, PolicyDeniedError
|
|
30
|
+
from control_zero.integrations.crewai.tool import GovernedCrewTool, wrap_crew_tools
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class GovernedCrewAgent:
|
|
34
|
+
"""
|
|
35
|
+
Governance wrapper for CrewAI Agent.
|
|
36
|
+
|
|
37
|
+
Wraps a CrewAI Agent to provide:
|
|
38
|
+
- Role-based policy enforcement
|
|
39
|
+
- Tool-level governance
|
|
40
|
+
- Action audit logging
|
|
41
|
+
- Execution tracking
|
|
42
|
+
|
|
43
|
+
Usage:
|
|
44
|
+
from crewai import Agent
|
|
45
|
+
|
|
46
|
+
researcher = Agent(
|
|
47
|
+
role="Senior Researcher",
|
|
48
|
+
goal="Research topics thoroughly",
|
|
49
|
+
backstory="You are an expert researcher...",
|
|
50
|
+
tools=[search_tool, scrape_tool],
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
governed_researcher = GovernedCrewAgent(
|
|
54
|
+
agent=researcher,
|
|
55
|
+
client=client,
|
|
56
|
+
wrap_tools=True,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Use in a crew
|
|
60
|
+
crew = Crew(
|
|
61
|
+
agents=[governed_researcher.agent],
|
|
62
|
+
tasks=[...],
|
|
63
|
+
)
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
agent: Agent,
|
|
69
|
+
client: ControlZeroClient,
|
|
70
|
+
wrap_tools: bool = True,
|
|
71
|
+
agent_name: Optional[str] = None,
|
|
72
|
+
allowed_tools: Optional[List[str]] = None,
|
|
73
|
+
):
|
|
74
|
+
"""
|
|
75
|
+
Initialize governed agent.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
agent: The CrewAI Agent to wrap
|
|
79
|
+
client: Control Zero client
|
|
80
|
+
wrap_tools: Whether to wrap agent tools with governance
|
|
81
|
+
agent_name: Name for logging (defaults to agent.role)
|
|
82
|
+
allowed_tools: List of allowed tool names (filters tools)
|
|
83
|
+
"""
|
|
84
|
+
if not CREWAI_AVAILABLE:
|
|
85
|
+
raise ImportError(
|
|
86
|
+
"crewai is required. Install with: pip install crewai"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
self._agent = agent
|
|
90
|
+
self._client = client
|
|
91
|
+
self._agent_name = agent_name or agent.role
|
|
92
|
+
self._allowed_tools = allowed_tools
|
|
93
|
+
|
|
94
|
+
# Filter tools if allowlist specified
|
|
95
|
+
if allowed_tools and agent.tools:
|
|
96
|
+
agent.tools = [
|
|
97
|
+
t for t in agent.tools
|
|
98
|
+
if getattr(t, 'name', str(t)) in allowed_tools
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
# Wrap tools with governance
|
|
102
|
+
if wrap_tools and agent.tools:
|
|
103
|
+
self._wrap_agent_tools()
|
|
104
|
+
|
|
105
|
+
# Tracking
|
|
106
|
+
self._total_actions = 0
|
|
107
|
+
self._session_start = time.time()
|
|
108
|
+
|
|
109
|
+
def _wrap_agent_tools(self) -> None:
|
|
110
|
+
"""Wrap all agent tools with governance."""
|
|
111
|
+
governed_tools = []
|
|
112
|
+
for tool in self._agent.tools:
|
|
113
|
+
if isinstance(tool, GovernedCrewTool):
|
|
114
|
+
governed_tools.append(tool)
|
|
115
|
+
elif isinstance(tool, CrewAIBaseTool):
|
|
116
|
+
governed_tools.append(
|
|
117
|
+
GovernedCrewTool(
|
|
118
|
+
base_tool=tool,
|
|
119
|
+
client=self._client,
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
else:
|
|
123
|
+
# Callable tools - wrap inline
|
|
124
|
+
governed_tools.append(tool)
|
|
125
|
+
self._agent.tools = governed_tools
|
|
126
|
+
|
|
127
|
+
def _check_policy(self, action: str) -> PolicyDecision:
|
|
128
|
+
"""Check policy for agent action."""
|
|
129
|
+
if hasattr(self._client, '_policy_cache') and self._client._policy_cache:
|
|
130
|
+
return self._client._policy_cache.evaluate(
|
|
131
|
+
f"crewai:agent:{self._agent_name}",
|
|
132
|
+
action
|
|
133
|
+
)
|
|
134
|
+
return PolicyDecision(effect="allow")
|
|
135
|
+
|
|
136
|
+
def execute_task(
|
|
137
|
+
self,
|
|
138
|
+
task: Any,
|
|
139
|
+
context: Optional[str] = None,
|
|
140
|
+
tools: Optional[List] = None,
|
|
141
|
+
) -> str:
|
|
142
|
+
"""
|
|
143
|
+
Execute a task with governance.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
task: The task to execute
|
|
147
|
+
context: Additional context
|
|
148
|
+
tools: Override tools for this execution
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Task result string
|
|
152
|
+
"""
|
|
153
|
+
decision = self._check_policy("execute_task")
|
|
154
|
+
|
|
155
|
+
if decision.effect == "deny":
|
|
156
|
+
self._log_action("execute_task", "denied", 0, decision)
|
|
157
|
+
raise PolicyDeniedError(decision)
|
|
158
|
+
|
|
159
|
+
start = time.perf_counter()
|
|
160
|
+
self._total_actions += 1
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
# Execute task using agent
|
|
164
|
+
result = self._agent.execute_task(
|
|
165
|
+
task=task,
|
|
166
|
+
context=context,
|
|
167
|
+
tools=tools or self._agent.tools,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
latency_ms = int((time.perf_counter() - start) * 1000)
|
|
171
|
+
self._log_action("execute_task", "success", latency_ms, decision, {
|
|
172
|
+
"task_description": str(task.description)[:200] if hasattr(task, 'description') else None,
|
|
173
|
+
"result_length": len(result) if result else 0,
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
return result
|
|
177
|
+
|
|
178
|
+
except PolicyDeniedError:
|
|
179
|
+
raise
|
|
180
|
+
except Exception as e:
|
|
181
|
+
latency_ms = int((time.perf_counter() - start) * 1000)
|
|
182
|
+
self._log_action("execute_task", "error", latency_ms, decision, error=e)
|
|
183
|
+
raise
|
|
184
|
+
|
|
185
|
+
def _log_action(
|
|
186
|
+
self,
|
|
187
|
+
action: str,
|
|
188
|
+
status: str,
|
|
189
|
+
latency_ms: int,
|
|
190
|
+
decision: PolicyDecision,
|
|
191
|
+
metadata: Optional[Dict] = None,
|
|
192
|
+
error: Optional[Exception] = None,
|
|
193
|
+
) -> None:
|
|
194
|
+
"""Log agent action."""
|
|
195
|
+
log_data = metadata or {}
|
|
196
|
+
log_data["agent_role"] = self._agent.role
|
|
197
|
+
log_data["total_actions"] = self._total_actions
|
|
198
|
+
|
|
199
|
+
self._client._log(
|
|
200
|
+
tool=f"crewai:agent:{self._agent_name}",
|
|
201
|
+
method=action,
|
|
202
|
+
status=status,
|
|
203
|
+
latency_ms=latency_ms,
|
|
204
|
+
policy_decision=decision,
|
|
205
|
+
error_type=type(error).__name__ if error else None,
|
|
206
|
+
error_message=str(error) if error else None,
|
|
207
|
+
**log_data
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
@property
|
|
211
|
+
def agent(self) -> Agent:
|
|
212
|
+
"""Get underlying CrewAI agent."""
|
|
213
|
+
return self._agent
|
|
214
|
+
|
|
215
|
+
@property
|
|
216
|
+
def role(self) -> str:
|
|
217
|
+
"""Get agent role."""
|
|
218
|
+
return self._agent.role
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
def goal(self) -> str:
|
|
222
|
+
"""Get agent goal."""
|
|
223
|
+
return self._agent.goal
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def tools(self) -> List:
|
|
227
|
+
"""Get agent tools."""
|
|
228
|
+
return self._agent.tools
|
|
229
|
+
|
|
230
|
+
@property
|
|
231
|
+
def total_actions(self) -> int:
|
|
232
|
+
"""Get total actions executed."""
|
|
233
|
+
return self._total_actions
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def governed_agent(
|
|
237
|
+
client: ControlZeroClient,
|
|
238
|
+
wrap_tools: bool = True,
|
|
239
|
+
allowed_tools: Optional[List[str]] = None,
|
|
240
|
+
) -> Callable:
|
|
241
|
+
"""
|
|
242
|
+
Decorator to create a governed CrewAI agent.
|
|
243
|
+
|
|
244
|
+
Usage:
|
|
245
|
+
@governed_agent(client, wrap_tools=True)
|
|
246
|
+
def create_researcher():
|
|
247
|
+
return Agent(
|
|
248
|
+
role="Researcher",
|
|
249
|
+
goal="Research topics",
|
|
250
|
+
backstory="You are an expert researcher",
|
|
251
|
+
tools=[search_tool],
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
researcher = create_researcher() # Returns GovernedCrewAgent
|
|
255
|
+
"""
|
|
256
|
+
def decorator(func: Callable) -> Callable:
|
|
257
|
+
@wraps(func)
|
|
258
|
+
def wrapper(*args, **kwargs) -> GovernedCrewAgent:
|
|
259
|
+
agent = func(*args, **kwargs)
|
|
260
|
+
return GovernedCrewAgent(
|
|
261
|
+
agent=agent,
|
|
262
|
+
client=client,
|
|
263
|
+
wrap_tools=wrap_tools,
|
|
264
|
+
allowed_tools=allowed_tools,
|
|
265
|
+
)
|
|
266
|
+
return wrapper
|
|
267
|
+
return decorator
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Governed CrewAI Crew wrapper.
|
|
3
|
+
|
|
4
|
+
Provides governance enforcement for CrewAI crews including:
|
|
5
|
+
- Crew-level policy enforcement
|
|
6
|
+
- Agent role-based access control
|
|
7
|
+
- Task execution governance
|
|
8
|
+
- Inter-agent communication logging
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import time
|
|
14
|
+
from typing import Any, Dict, List, Optional
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from crewai import Crew, Agent, Task, Process
|
|
18
|
+
CREWAI_AVAILABLE = True
|
|
19
|
+
except ImportError:
|
|
20
|
+
CREWAI_AVAILABLE = False
|
|
21
|
+
class Crew:
|
|
22
|
+
pass
|
|
23
|
+
class Agent:
|
|
24
|
+
pass
|
|
25
|
+
class Task:
|
|
26
|
+
pass
|
|
27
|
+
class Process:
|
|
28
|
+
sequential = "sequential"
|
|
29
|
+
hierarchical = "hierarchical"
|
|
30
|
+
|
|
31
|
+
from control_zero.client import ControlZeroClient
|
|
32
|
+
from control_zero.policy import PolicyDecision, PolicyDeniedError
|
|
33
|
+
from control_zero.integrations.crewai.agent import GovernedCrewAgent
|
|
34
|
+
from control_zero.integrations.crewai.task import GovernedTask
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class GovernedCrew:
|
|
38
|
+
"""
|
|
39
|
+
Governance wrapper for CrewAI Crew.
|
|
40
|
+
|
|
41
|
+
Wraps a CrewAI Crew to provide:
|
|
42
|
+
- Crew-level policy enforcement
|
|
43
|
+
- Automatic agent and task governance wrapping
|
|
44
|
+
- Execution audit logging
|
|
45
|
+
- Resource tracking
|
|
46
|
+
|
|
47
|
+
Usage:
|
|
48
|
+
from crewai import Crew, Agent, Task
|
|
49
|
+
|
|
50
|
+
researcher = Agent(role="Researcher", ...)
|
|
51
|
+
writer = Agent(role="Writer", ...)
|
|
52
|
+
|
|
53
|
+
research_task = Task(description="...", agent=researcher)
|
|
54
|
+
writing_task = Task(description="...", agent=writer)
|
|
55
|
+
|
|
56
|
+
crew = Crew(
|
|
57
|
+
agents=[researcher, writer],
|
|
58
|
+
tasks=[research_task, writing_task],
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
governed_crew = GovernedCrew(
|
|
62
|
+
crew=crew,
|
|
63
|
+
client=client,
|
|
64
|
+
crew_name="content_creation",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
result = governed_crew.kickoff()
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
crew: Crew,
|
|
73
|
+
client: ControlZeroClient,
|
|
74
|
+
crew_name: str = "crewai_crew",
|
|
75
|
+
wrap_agents: bool = True,
|
|
76
|
+
wrap_tasks: bool = True,
|
|
77
|
+
max_rpm: Optional[int] = None,
|
|
78
|
+
max_iterations: Optional[int] = None,
|
|
79
|
+
log_outputs: bool = False,
|
|
80
|
+
):
|
|
81
|
+
"""
|
|
82
|
+
Initialize governed crew.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
crew: The CrewAI Crew to wrap
|
|
86
|
+
client: Control Zero client
|
|
87
|
+
crew_name: Name for logging purposes
|
|
88
|
+
wrap_agents: Whether to wrap agents with governance
|
|
89
|
+
wrap_tasks: Whether to wrap tasks with governance
|
|
90
|
+
max_rpm: Maximum requests per minute (rate limit)
|
|
91
|
+
max_iterations: Maximum iterations for the crew
|
|
92
|
+
log_outputs: Whether to log output content
|
|
93
|
+
"""
|
|
94
|
+
if not CREWAI_AVAILABLE:
|
|
95
|
+
raise ImportError(
|
|
96
|
+
"crewai is required. Install with: pip install crewai"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
self._crew = crew
|
|
100
|
+
self._client = client
|
|
101
|
+
self._crew_name = crew_name
|
|
102
|
+
self._log_outputs = log_outputs
|
|
103
|
+
|
|
104
|
+
# Apply limits
|
|
105
|
+
if max_rpm:
|
|
106
|
+
self._crew.max_rpm = max_rpm
|
|
107
|
+
if max_iterations:
|
|
108
|
+
self._crew.max_iterations = max_iterations
|
|
109
|
+
|
|
110
|
+
# Wrap agents and tasks with governance
|
|
111
|
+
if wrap_agents:
|
|
112
|
+
self._governed_agents = self._wrap_agents()
|
|
113
|
+
else:
|
|
114
|
+
self._governed_agents = []
|
|
115
|
+
|
|
116
|
+
if wrap_tasks:
|
|
117
|
+
self._governed_tasks = self._wrap_tasks()
|
|
118
|
+
else:
|
|
119
|
+
self._governed_tasks = []
|
|
120
|
+
|
|
121
|
+
# Tracking
|
|
122
|
+
self._kickoff_count = 0
|
|
123
|
+
self._total_tokens = 0
|
|
124
|
+
self._session_start = time.time()
|
|
125
|
+
|
|
126
|
+
def _wrap_agents(self) -> List[GovernedCrewAgent]:
|
|
127
|
+
"""Wrap crew agents with governance."""
|
|
128
|
+
governed = []
|
|
129
|
+
for agent in self._crew.agents:
|
|
130
|
+
if isinstance(agent, GovernedCrewAgent):
|
|
131
|
+
governed.append(agent)
|
|
132
|
+
else:
|
|
133
|
+
governed.append(
|
|
134
|
+
GovernedCrewAgent(
|
|
135
|
+
agent=agent,
|
|
136
|
+
client=self._client,
|
|
137
|
+
wrap_tools=True,
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
return governed
|
|
141
|
+
|
|
142
|
+
def _wrap_tasks(self) -> List[GovernedTask]:
|
|
143
|
+
"""Wrap crew tasks with governance."""
|
|
144
|
+
governed = []
|
|
145
|
+
for task in self._crew.tasks:
|
|
146
|
+
if isinstance(task, GovernedTask):
|
|
147
|
+
governed.append(task)
|
|
148
|
+
else:
|
|
149
|
+
governed.append(
|
|
150
|
+
GovernedTask(
|
|
151
|
+
task=task,
|
|
152
|
+
client=self._client,
|
|
153
|
+
log_outputs=self._log_outputs,
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
return governed
|
|
157
|
+
|
|
158
|
+
def _check_policy(self, action: str) -> PolicyDecision:
|
|
159
|
+
"""Check policy for crew action."""
|
|
160
|
+
if hasattr(self._client, '_policy_cache') and self._client._policy_cache:
|
|
161
|
+
return self._client._policy_cache.evaluate(
|
|
162
|
+
f"crewai:crew:{self._crew_name}",
|
|
163
|
+
action
|
|
164
|
+
)
|
|
165
|
+
return PolicyDecision(effect="allow")
|
|
166
|
+
|
|
167
|
+
def kickoff(
|
|
168
|
+
self,
|
|
169
|
+
inputs: Optional[Dict[str, Any]] = None,
|
|
170
|
+
) -> Any:
|
|
171
|
+
"""
|
|
172
|
+
Execute the crew with governance.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
inputs: Input variables for the crew
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
Crew execution result
|
|
179
|
+
"""
|
|
180
|
+
decision = self._check_policy("kickoff")
|
|
181
|
+
|
|
182
|
+
if decision.effect == "deny":
|
|
183
|
+
self._log_kickoff("denied", 0, decision)
|
|
184
|
+
raise PolicyDeniedError(decision)
|
|
185
|
+
|
|
186
|
+
start = time.perf_counter()
|
|
187
|
+
self._kickoff_count += 1
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
# Execute crew
|
|
191
|
+
result = self._crew.kickoff(inputs=inputs)
|
|
192
|
+
|
|
193
|
+
latency_ms = int((time.perf_counter() - start) * 1000)
|
|
194
|
+
|
|
195
|
+
metadata = {
|
|
196
|
+
"kickoff_count": self._kickoff_count,
|
|
197
|
+
"num_agents": len(self._crew.agents),
|
|
198
|
+
"num_tasks": len(self._crew.tasks),
|
|
199
|
+
"process_type": str(getattr(self._crew, 'process', 'sequential')),
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if self._log_outputs and result:
|
|
203
|
+
metadata["result_preview"] = str(result)[:500]
|
|
204
|
+
|
|
205
|
+
self._log_kickoff("success", latency_ms, decision, metadata)
|
|
206
|
+
|
|
207
|
+
return result
|
|
208
|
+
|
|
209
|
+
except PolicyDeniedError:
|
|
210
|
+
raise
|
|
211
|
+
except Exception as e:
|
|
212
|
+
latency_ms = int((time.perf_counter() - start) * 1000)
|
|
213
|
+
self._log_kickoff("error", latency_ms, decision, error=e)
|
|
214
|
+
raise
|
|
215
|
+
|
|
216
|
+
async def akickoff(
|
|
217
|
+
self,
|
|
218
|
+
inputs: Optional[Dict[str, Any]] = None,
|
|
219
|
+
) -> Any:
|
|
220
|
+
"""
|
|
221
|
+
Async execute the crew with governance.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
inputs: Input variables for the crew
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Crew execution result
|
|
228
|
+
"""
|
|
229
|
+
decision = self._check_policy("kickoff")
|
|
230
|
+
|
|
231
|
+
if decision.effect == "deny":
|
|
232
|
+
self._log_kickoff("denied", 0, decision)
|
|
233
|
+
raise PolicyDeniedError(decision)
|
|
234
|
+
|
|
235
|
+
start = time.perf_counter()
|
|
236
|
+
self._kickoff_count += 1
|
|
237
|
+
|
|
238
|
+
try:
|
|
239
|
+
# Execute crew async
|
|
240
|
+
if hasattr(self._crew, 'akickoff'):
|
|
241
|
+
result = await self._crew.akickoff(inputs=inputs)
|
|
242
|
+
else:
|
|
243
|
+
# Fallback to sync
|
|
244
|
+
result = self._crew.kickoff(inputs=inputs)
|
|
245
|
+
|
|
246
|
+
latency_ms = int((time.perf_counter() - start) * 1000)
|
|
247
|
+
|
|
248
|
+
metadata = {
|
|
249
|
+
"kickoff_count": self._kickoff_count,
|
|
250
|
+
"num_agents": len(self._crew.agents),
|
|
251
|
+
"num_tasks": len(self._crew.tasks),
|
|
252
|
+
"async": True,
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if self._log_outputs and result:
|
|
256
|
+
metadata["result_preview"] = str(result)[:500]
|
|
257
|
+
|
|
258
|
+
self._log_kickoff("success", latency_ms, decision, metadata)
|
|
259
|
+
|
|
260
|
+
return result
|
|
261
|
+
|
|
262
|
+
except PolicyDeniedError:
|
|
263
|
+
raise
|
|
264
|
+
except Exception as e:
|
|
265
|
+
latency_ms = int((time.perf_counter() - start) * 1000)
|
|
266
|
+
self._log_kickoff("error", latency_ms, decision, error=e)
|
|
267
|
+
raise
|
|
268
|
+
|
|
269
|
+
def kickoff_for_each(
|
|
270
|
+
self,
|
|
271
|
+
inputs: List[Dict[str, Any]],
|
|
272
|
+
) -> List[Any]:
|
|
273
|
+
"""
|
|
274
|
+
Execute crew for each input set with governance.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
inputs: List of input dictionaries
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
List of results
|
|
281
|
+
"""
|
|
282
|
+
decision = self._check_policy("kickoff_for_each")
|
|
283
|
+
|
|
284
|
+
if decision.effect == "deny":
|
|
285
|
+
self._log_kickoff("denied", 0, decision, {"action": "kickoff_for_each"})
|
|
286
|
+
raise PolicyDeniedError(decision)
|
|
287
|
+
|
|
288
|
+
start = time.perf_counter()
|
|
289
|
+
results = []
|
|
290
|
+
|
|
291
|
+
try:
|
|
292
|
+
for input_set in inputs:
|
|
293
|
+
result = self._crew.kickoff(inputs=input_set)
|
|
294
|
+
results.append(result)
|
|
295
|
+
self._kickoff_count += 1
|
|
296
|
+
|
|
297
|
+
latency_ms = int((time.perf_counter() - start) * 1000)
|
|
298
|
+
|
|
299
|
+
self._log_kickoff("success", latency_ms, decision, {
|
|
300
|
+
"action": "kickoff_for_each",
|
|
301
|
+
"batch_size": len(inputs),
|
|
302
|
+
"successful": len(results),
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
return results
|
|
306
|
+
|
|
307
|
+
except PolicyDeniedError:
|
|
308
|
+
raise
|
|
309
|
+
except Exception as e:
|
|
310
|
+
latency_ms = int((time.perf_counter() - start) * 1000)
|
|
311
|
+
self._log_kickoff("error", latency_ms, decision, {
|
|
312
|
+
"action": "kickoff_for_each",
|
|
313
|
+
"completed": len(results),
|
|
314
|
+
}, error=e)
|
|
315
|
+
raise
|
|
316
|
+
|
|
317
|
+
def _log_kickoff(
|
|
318
|
+
self,
|
|
319
|
+
status: str,
|
|
320
|
+
latency_ms: int,
|
|
321
|
+
decision: PolicyDecision,
|
|
322
|
+
metadata: Optional[Dict] = None,
|
|
323
|
+
error: Optional[Exception] = None,
|
|
324
|
+
) -> None:
|
|
325
|
+
"""Log crew kickoff."""
|
|
326
|
+
log_data = metadata or {}
|
|
327
|
+
log_data["crew_name"] = self._crew_name
|
|
328
|
+
|
|
329
|
+
self._client._log(
|
|
330
|
+
tool=f"crewai:crew:{self._crew_name}",
|
|
331
|
+
method="kickoff",
|
|
332
|
+
status=status,
|
|
333
|
+
latency_ms=latency_ms,
|
|
334
|
+
policy_decision=decision,
|
|
335
|
+
error_type=type(error).__name__ if error else None,
|
|
336
|
+
error_message=str(error) if error else None,
|
|
337
|
+
**log_data
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
@property
|
|
341
|
+
def crew(self) -> Crew:
|
|
342
|
+
"""Get underlying CrewAI crew."""
|
|
343
|
+
return self._crew
|
|
344
|
+
|
|
345
|
+
@property
|
|
346
|
+
def agents(self) -> List:
|
|
347
|
+
"""Get crew agents."""
|
|
348
|
+
return self._crew.agents
|
|
349
|
+
|
|
350
|
+
@property
|
|
351
|
+
def tasks(self) -> List:
|
|
352
|
+
"""Get crew tasks."""
|
|
353
|
+
return self._crew.tasks
|
|
354
|
+
|
|
355
|
+
@property
|
|
356
|
+
def governed_agents(self) -> List[GovernedCrewAgent]:
|
|
357
|
+
"""Get governed agent wrappers."""
|
|
358
|
+
return self._governed_agents
|
|
359
|
+
|
|
360
|
+
@property
|
|
361
|
+
def governed_tasks(self) -> List[GovernedTask]:
|
|
362
|
+
"""Get governed task wrappers."""
|
|
363
|
+
return self._governed_tasks
|
|
364
|
+
|
|
365
|
+
@property
|
|
366
|
+
def kickoff_count(self) -> int:
|
|
367
|
+
"""Get total kickoff count."""
|
|
368
|
+
return self._kickoff_count
|
|
369
|
+
|
|
370
|
+
@property
|
|
371
|
+
def usage_metrics(self) -> Dict[str, Any]:
|
|
372
|
+
"""Get usage metrics for the crew."""
|
|
373
|
+
session_duration = time.time() - self._session_start
|
|
374
|
+
return {
|
|
375
|
+
"crew_name": self._crew_name,
|
|
376
|
+
"kickoff_count": self._kickoff_count,
|
|
377
|
+
"num_agents": len(self._crew.agents),
|
|
378
|
+
"num_tasks": len(self._crew.tasks),
|
|
379
|
+
"session_duration_seconds": round(session_duration, 2),
|
|
380
|
+
"total_tokens": self._total_tokens,
|
|
381
|
+
}
|