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.
Files changed (44) hide show
  1. control_zero/__init__.py +31 -0
  2. control_zero/client.py +584 -0
  3. control_zero/integrations/crewai/__init__.py +53 -0
  4. control_zero/integrations/crewai/agent.py +267 -0
  5. control_zero/integrations/crewai/crew.py +381 -0
  6. control_zero/integrations/crewai/task.py +291 -0
  7. control_zero/integrations/crewai/tool.py +299 -0
  8. control_zero/integrations/langchain/__init__.py +58 -0
  9. control_zero/integrations/langchain/agent.py +311 -0
  10. control_zero/integrations/langchain/callbacks.py +441 -0
  11. control_zero/integrations/langchain/chain.py +319 -0
  12. control_zero/integrations/langchain/graph.py +441 -0
  13. control_zero/integrations/langchain/tool.py +271 -0
  14. control_zero/llm/__init__.py +77 -0
  15. control_zero/llm/anthropic/__init__.py +35 -0
  16. control_zero/llm/anthropic/client.py +136 -0
  17. control_zero/llm/anthropic/messages.py +375 -0
  18. control_zero/llm/base.py +551 -0
  19. control_zero/llm/cohere/__init__.py +32 -0
  20. control_zero/llm/cohere/client.py +402 -0
  21. control_zero/llm/gemini/__init__.py +34 -0
  22. control_zero/llm/gemini/client.py +486 -0
  23. control_zero/llm/groq/__init__.py +32 -0
  24. control_zero/llm/groq/client.py +330 -0
  25. control_zero/llm/mistral/__init__.py +32 -0
  26. control_zero/llm/mistral/client.py +319 -0
  27. control_zero/llm/ollama/__init__.py +31 -0
  28. control_zero/llm/ollama/client.py +439 -0
  29. control_zero/llm/openai/__init__.py +34 -0
  30. control_zero/llm/openai/chat.py +331 -0
  31. control_zero/llm/openai/client.py +182 -0
  32. control_zero/logging/__init__.py +5 -0
  33. control_zero/logging/async_logger.py +65 -0
  34. control_zero/mcp/__init__.py +5 -0
  35. control_zero/mcp/middleware.py +148 -0
  36. control_zero/policy/__init__.py +5 -0
  37. control_zero/policy/enforcer.py +99 -0
  38. control_zero/secrets/__init__.py +5 -0
  39. control_zero/secrets/manager.py +77 -0
  40. control_zero/types.py +51 -0
  41. control_zero-0.2.0.dist-info/METADATA +216 -0
  42. control_zero-0.2.0.dist-info/RECORD +44 -0
  43. control_zero-0.2.0.dist-info/WHEEL +4 -0
  44. control_zero-0.2.0.dist-info/licenses/LICENSE +17 -0
@@ -0,0 +1,291 @@
1
+ """
2
+ Governed CrewAI Task wrapper.
3
+
4
+ Provides governance enforcement for CrewAI tasks including:
5
+ - Task-level policy checks
6
+ - Delegation governance
7
+ - Output validation
8
+ - Execution logging
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 Task, Agent
19
+ CREWAI_AVAILABLE = True
20
+ except ImportError:
21
+ CREWAI_AVAILABLE = False
22
+ class Task:
23
+ pass
24
+ class Agent:
25
+ pass
26
+
27
+ from control_zero.client import ControlZeroClient
28
+ from control_zero.policy import PolicyDecision, PolicyDeniedError
29
+
30
+
31
+ class GovernedTask:
32
+ """
33
+ Governance wrapper for CrewAI Task.
34
+
35
+ Wraps a CrewAI Task to provide:
36
+ - Task execution policy enforcement
37
+ - Delegation governance
38
+ - Output validation
39
+ - Execution audit logging
40
+
41
+ Usage:
42
+ from crewai import Task
43
+
44
+ research_task = Task(
45
+ description="Research the topic...",
46
+ agent=researcher,
47
+ expected_output="A detailed report...",
48
+ )
49
+
50
+ governed_task = GovernedTask(
51
+ task=research_task,
52
+ client=client,
53
+ task_name="research_task",
54
+ )
55
+ """
56
+
57
+ def __init__(
58
+ self,
59
+ task: Task,
60
+ client: ControlZeroClient,
61
+ task_name: Optional[str] = None,
62
+ allow_delegation: bool = True,
63
+ max_delegation_depth: int = 3,
64
+ log_outputs: bool = False,
65
+ ):
66
+ """
67
+ Initialize governed task.
68
+
69
+ Args:
70
+ task: The CrewAI Task to wrap
71
+ client: Control Zero client
72
+ task_name: Name for logging purposes
73
+ allow_delegation: Whether to allow task delegation
74
+ max_delegation_depth: Maximum delegation depth
75
+ log_outputs: Whether to log output content
76
+ """
77
+ if not CREWAI_AVAILABLE:
78
+ raise ImportError(
79
+ "crewai is required. Install with: pip install crewai"
80
+ )
81
+
82
+ self._task = task
83
+ self._client = client
84
+ self._task_name = task_name or self._extract_task_name(task)
85
+ self._allow_delegation = allow_delegation
86
+ self._max_delegation_depth = max_delegation_depth
87
+ self._log_outputs = log_outputs
88
+
89
+ # Tracking
90
+ self._delegation_depth = 0
91
+ self._execution_count = 0
92
+
93
+ def _extract_task_name(self, task: Task) -> str:
94
+ """Extract a name from task description."""
95
+ if hasattr(task, 'description'):
96
+ # Take first 30 chars of description
97
+ desc = str(task.description)[:30]
98
+ return desc.replace(" ", "_").lower()
99
+ return "unnamed_task"
100
+
101
+ def _check_policy(self, action: str) -> PolicyDecision:
102
+ """Check policy for task action."""
103
+ if hasattr(self._client, '_policy_cache') and self._client._policy_cache:
104
+ return self._client._policy_cache.evaluate(
105
+ f"crewai:task:{self._task_name}",
106
+ action
107
+ )
108
+ return PolicyDecision(effect="allow")
109
+
110
+ def execute(
111
+ self,
112
+ agent: Optional[Agent] = None,
113
+ context: Optional[str] = None,
114
+ tools: Optional[List] = None,
115
+ ) -> Any:
116
+ """
117
+ Execute the task with governance.
118
+
119
+ Args:
120
+ agent: Agent to execute (overrides task.agent)
121
+ context: Additional context
122
+ tools: Override tools for execution
123
+
124
+ Returns:
125
+ Task output
126
+ """
127
+ decision = self._check_policy("execute")
128
+
129
+ if decision.effect == "deny":
130
+ self._log_execution("denied", 0, decision)
131
+ raise PolicyDeniedError(decision)
132
+
133
+ start = time.perf_counter()
134
+ self._execution_count += 1
135
+
136
+ try:
137
+ # Check delegation policy
138
+ if self._delegation_depth >= self._max_delegation_depth:
139
+ raise PolicyDeniedError(PolicyDecision(
140
+ effect="deny",
141
+ reason=f"Maximum delegation depth ({self._max_delegation_depth}) exceeded"
142
+ ))
143
+
144
+ # Execute task
145
+ executing_agent = agent or self._task.agent
146
+ result = self._task.execute(
147
+ agent=executing_agent,
148
+ context=context,
149
+ tools=tools,
150
+ )
151
+
152
+ latency_ms = int((time.perf_counter() - start) * 1000)
153
+
154
+ metadata = {
155
+ "execution_count": self._execution_count,
156
+ "delegation_depth": self._delegation_depth,
157
+ }
158
+
159
+ if self._log_outputs and result:
160
+ metadata["output_preview"] = str(result)[:200]
161
+
162
+ self._log_execution("success", latency_ms, decision, metadata)
163
+
164
+ return result
165
+
166
+ except PolicyDeniedError:
167
+ raise
168
+ except Exception as e:
169
+ latency_ms = int((time.perf_counter() - start) * 1000)
170
+ self._log_execution("error", latency_ms, decision, error=e)
171
+ raise
172
+
173
+ def delegate(
174
+ self,
175
+ to_agent: Agent,
176
+ context: Optional[str] = None,
177
+ ) -> Any:
178
+ """
179
+ Delegate task to another agent with governance.
180
+
181
+ Args:
182
+ to_agent: Agent to delegate to
183
+ context: Context for delegation
184
+
185
+ Returns:
186
+ Delegation result
187
+ """
188
+ if not self._allow_delegation:
189
+ decision = PolicyDecision(
190
+ effect="deny",
191
+ reason="Task delegation is not allowed"
192
+ )
193
+ self._log_execution("denied", 0, decision, {"reason": "delegation_disabled"})
194
+ raise PolicyDeniedError(decision)
195
+
196
+ decision = self._check_policy("delegate")
197
+
198
+ if decision.effect == "deny":
199
+ self._log_execution("denied", 0, decision, {"action": "delegate"})
200
+ raise PolicyDeniedError(decision)
201
+
202
+ # Increment delegation depth
203
+ self._delegation_depth += 1
204
+
205
+ try:
206
+ return self.execute(agent=to_agent, context=context)
207
+ finally:
208
+ self._delegation_depth -= 1
209
+
210
+ def _log_execution(
211
+ self,
212
+ status: str,
213
+ latency_ms: int,
214
+ decision: PolicyDecision,
215
+ metadata: Optional[Dict] = None,
216
+ error: Optional[Exception] = None,
217
+ ) -> None:
218
+ """Log task execution."""
219
+ log_data = metadata or {}
220
+ log_data["task_name"] = self._task_name
221
+
222
+ if hasattr(self._task, 'description'):
223
+ log_data["description_preview"] = str(self._task.description)[:100]
224
+
225
+ self._client._log(
226
+ tool=f"crewai:task:{self._task_name}",
227
+ method="execute",
228
+ status=status,
229
+ latency_ms=latency_ms,
230
+ policy_decision=decision,
231
+ error_type=type(error).__name__ if error else None,
232
+ error_message=str(error) if error else None,
233
+ **log_data
234
+ )
235
+
236
+ @property
237
+ def task(self) -> Task:
238
+ """Get underlying CrewAI task."""
239
+ return self._task
240
+
241
+ @property
242
+ def description(self) -> str:
243
+ """Get task description."""
244
+ return getattr(self._task, 'description', '')
245
+
246
+ @property
247
+ def agent(self) -> Optional[Agent]:
248
+ """Get assigned agent."""
249
+ return getattr(self._task, 'agent', None)
250
+
251
+ @property
252
+ def execution_count(self) -> int:
253
+ """Get total execution count."""
254
+ return self._execution_count
255
+
256
+
257
+ def governed_task(
258
+ client: ControlZeroClient,
259
+ task_name: Optional[str] = None,
260
+ allow_delegation: bool = True,
261
+ max_delegation_depth: int = 3,
262
+ log_outputs: bool = False,
263
+ ) -> Callable:
264
+ """
265
+ Decorator to create a governed CrewAI task.
266
+
267
+ Usage:
268
+ @governed_task(client, task_name="research")
269
+ def create_research_task(agent):
270
+ return Task(
271
+ description="Research the topic...",
272
+ agent=agent,
273
+ expected_output="A detailed report",
274
+ )
275
+
276
+ task = create_research_task(researcher) # Returns GovernedTask
277
+ """
278
+ def decorator(func: Callable) -> Callable:
279
+ @wraps(func)
280
+ def wrapper(*args, **kwargs) -> GovernedTask:
281
+ task = func(*args, **kwargs)
282
+ return GovernedTask(
283
+ task=task,
284
+ client=client,
285
+ task_name=task_name,
286
+ allow_delegation=allow_delegation,
287
+ max_delegation_depth=max_delegation_depth,
288
+ log_outputs=log_outputs,
289
+ )
290
+ return wrapper
291
+ return decorator
@@ -0,0 +1,299 @@
1
+ """
2
+ Governed CrewAI Tool wrappers.
3
+
4
+ Provides governance enforcement for CrewAI tools including:
5
+ - Policy checks before execution
6
+ - Audit logging
7
+ - Error tracking
8
+ - Secret injection
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, Type
16
+
17
+ try:
18
+ from crewai.tools import BaseTool as CrewAIBaseTool
19
+ from pydantic import BaseModel, Field
20
+ CREWAI_AVAILABLE = True
21
+ except ImportError:
22
+ CREWAI_AVAILABLE = False
23
+ class CrewAIBaseTool:
24
+ pass
25
+ class BaseModel:
26
+ pass
27
+
28
+ from control_zero.client import ControlZeroClient
29
+ from control_zero.policy import PolicyDecision, PolicyDeniedError
30
+
31
+
32
+ class GovernedCrewTool(CrewAIBaseTool):
33
+ """
34
+ Wraps a CrewAI Tool to enforce Control Zero governance policies.
35
+
36
+ Usage:
37
+ from crewai.tools import BaseTool
38
+
39
+ class MyTool(BaseTool):
40
+ name: str = "my_tool"
41
+ description: str = "Does something"
42
+
43
+ def _run(self, query: str) -> str:
44
+ return "result"
45
+
46
+ governed_tool = GovernedCrewTool(
47
+ base_tool=MyTool(),
48
+ client=client,
49
+ )
50
+ """
51
+
52
+ name: str = ""
53
+ description: str = ""
54
+ base_tool: Any = None
55
+ client: Any = None
56
+ tool_name: str = ""
57
+
58
+ class Config:
59
+ arbitrary_types_allowed = True
60
+
61
+ def __init__(
62
+ self,
63
+ base_tool: CrewAIBaseTool,
64
+ client: ControlZeroClient,
65
+ tool_name: Optional[str] = None,
66
+ **kwargs
67
+ ):
68
+ if not CREWAI_AVAILABLE:
69
+ raise ImportError(
70
+ "crewai is required for CrewAI integration. "
71
+ "Install with: pip install crewai"
72
+ )
73
+
74
+ name = base_tool.name
75
+ description = base_tool.description
76
+ tool_name = tool_name or name
77
+
78
+ super().__init__(
79
+ name=name,
80
+ description=description,
81
+ base_tool=base_tool,
82
+ client=client,
83
+ tool_name=tool_name,
84
+ **kwargs
85
+ )
86
+
87
+ def _run(self, *args: Any, **kwargs: Any) -> Any:
88
+ """Execute the tool with governance checks."""
89
+ method = "run"
90
+
91
+ # 1. Check Policy
92
+ decision = self._check_policy(method)
93
+
94
+ if decision.effect == "deny":
95
+ self._log_denied(method, decision)
96
+ raise PolicyDeniedError(decision)
97
+
98
+ start = time.perf_counter()
99
+ try:
100
+ # 2. Inject secrets if needed
101
+ kwargs = self._inject_secrets(kwargs)
102
+
103
+ # 3. Run Tool
104
+ result = self.base_tool._run(*args, **kwargs)
105
+
106
+ # 4. Log Success
107
+ latency_ms = int((time.perf_counter() - start) * 1000)
108
+ self._log_success(method, latency_ms, decision)
109
+
110
+ return result
111
+
112
+ except PolicyDeniedError:
113
+ raise
114
+ except Exception as e:
115
+ # 5. Log Error
116
+ latency_ms = int((time.perf_counter() - start) * 1000)
117
+ self._log_error(method, latency_ms, decision, e)
118
+ raise
119
+
120
+ async def _arun(self, *args: Any, **kwargs: Any) -> Any:
121
+ """Async execution with governance."""
122
+ method = "run"
123
+
124
+ decision = self._check_policy(method)
125
+
126
+ if decision.effect == "deny":
127
+ self._log_denied(method, decision)
128
+ raise PolicyDeniedError(decision)
129
+
130
+ start = time.perf_counter()
131
+ try:
132
+ kwargs = self._inject_secrets(kwargs)
133
+
134
+ # Check if base tool has async support
135
+ if hasattr(self.base_tool, '_arun'):
136
+ result = await self.base_tool._arun(*args, **kwargs)
137
+ else:
138
+ result = self.base_tool._run(*args, **kwargs)
139
+
140
+ latency_ms = int((time.perf_counter() - start) * 1000)
141
+ self._log_success(method, latency_ms, decision)
142
+
143
+ return result
144
+
145
+ except PolicyDeniedError:
146
+ raise
147
+ except Exception as e:
148
+ latency_ms = int((time.perf_counter() - start) * 1000)
149
+ self._log_error(method, latency_ms, decision, e)
150
+ raise
151
+
152
+ def _check_policy(self, method: str) -> PolicyDecision:
153
+ """Check policy for tool execution."""
154
+ if hasattr(self.client, '_policy_cache') and self.client._policy_cache:
155
+ return self.client._policy_cache.evaluate(self.tool_name, method)
156
+ return PolicyDecision(effect="allow")
157
+
158
+ def _inject_secrets(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:
159
+ """Inject secrets into kwargs if configured."""
160
+ if hasattr(self.client, '_secret_manager') and self.client._secret_manager:
161
+ return self.client._secret_manager.inject(self.tool_name, kwargs)
162
+ return kwargs
163
+
164
+ def _log_denied(self, method: str, decision: PolicyDecision) -> None:
165
+ """Log a denied execution."""
166
+ self.client._log(
167
+ tool=self.tool_name,
168
+ method=method,
169
+ status="denied",
170
+ latency_ms=0,
171
+ policy_decision=decision
172
+ )
173
+
174
+ def _log_success(self, method: str, latency_ms: int, decision: PolicyDecision) -> None:
175
+ """Log successful execution."""
176
+ self.client._log(
177
+ tool=self.tool_name,
178
+ method=method,
179
+ status="success",
180
+ latency_ms=latency_ms,
181
+ policy_decision=decision
182
+ )
183
+
184
+ def _log_error(
185
+ self,
186
+ method: str,
187
+ latency_ms: int,
188
+ decision: PolicyDecision,
189
+ error: Exception
190
+ ) -> None:
191
+ """Log execution error."""
192
+ self.client._log(
193
+ tool=self.tool_name,
194
+ method=method,
195
+ status="error",
196
+ latency_ms=latency_ms,
197
+ policy_decision=decision,
198
+ error_type=type(error).__name__,
199
+ error_message=str(error)
200
+ )
201
+
202
+
203
+ def governed_crew_tool(
204
+ client: ControlZeroClient,
205
+ tool_name: Optional[str] = None,
206
+ ) -> Callable:
207
+ """
208
+ Decorator to create a governed CrewAI tool from a function.
209
+
210
+ Usage:
211
+ @governed_crew_tool(client, tool_name="search")
212
+ def search_web(query: str) -> str:
213
+ '''Search the web for information.'''
214
+ return search(query)
215
+ """
216
+ def decorator(func: Callable) -> Callable:
217
+ if not CREWAI_AVAILABLE:
218
+ raise ImportError("crewai is required")
219
+
220
+ name = tool_name or func.__name__
221
+ description = func.__doc__ or ""
222
+
223
+ @wraps(func)
224
+ def governed_func(*args, **kwargs):
225
+ method = "run"
226
+ decision = PolicyDecision(effect="allow")
227
+
228
+ if hasattr(client, '_policy_cache') and client._policy_cache:
229
+ decision = client._policy_cache.evaluate(name, method)
230
+
231
+ if decision.effect == "deny":
232
+ client._log(
233
+ tool=name,
234
+ method=method,
235
+ status="denied",
236
+ latency_ms=0,
237
+ policy_decision=decision
238
+ )
239
+ raise PolicyDeniedError(decision)
240
+
241
+ start = time.perf_counter()
242
+ try:
243
+ # Inject secrets
244
+ if hasattr(client, '_secret_manager') and client._secret_manager:
245
+ kwargs = client._secret_manager.inject(name, kwargs)
246
+
247
+ result = func(*args, **kwargs)
248
+
249
+ latency_ms = int((time.perf_counter() - start) * 1000)
250
+ client._log(
251
+ tool=name,
252
+ method=method,
253
+ status="success",
254
+ latency_ms=latency_ms,
255
+ policy_decision=decision
256
+ )
257
+
258
+ return result
259
+
260
+ except PolicyDeniedError:
261
+ raise
262
+ except Exception as e:
263
+ latency_ms = int((time.perf_counter() - start) * 1000)
264
+ client._log(
265
+ tool=name,
266
+ method=method,
267
+ status="error",
268
+ latency_ms=latency_ms,
269
+ policy_decision=decision,
270
+ error_type=type(e).__name__,
271
+ error_message=str(e)
272
+ )
273
+ raise
274
+
275
+ # Return as a simple callable with metadata
276
+ governed_func.name = name
277
+ governed_func.description = description
278
+ governed_func.is_governed = True
279
+
280
+ return governed_func
281
+
282
+ return decorator
283
+
284
+
285
+ def wrap_crew_tools(
286
+ tools: List[CrewAIBaseTool],
287
+ client: ControlZeroClient,
288
+ ) -> List[GovernedCrewTool]:
289
+ """
290
+ Wrap multiple CrewAI tools with governance.
291
+
292
+ Usage:
293
+ tools = [tool1, tool2, tool3]
294
+ governed_tools = wrap_crew_tools(tools, client)
295
+ """
296
+ return [
297
+ GovernedCrewTool(base_tool=tool, client=client)
298
+ for tool in tools
299
+ ]
@@ -0,0 +1,58 @@
1
+ """
2
+ Control Zero LangChain Integration.
3
+
4
+ This module provides governance wrappers for LangChain components including:
5
+ - Tools (GovernedTool, GovernanceTool)
6
+ - Agents (GovernedAgent)
7
+ - Chains (GovernedChain)
8
+ - LangGraph (GovernedNode, governed_graph)
9
+ - Callbacks (ControlZeroCallbackHandler)
10
+
11
+ Usage:
12
+ from control_zero import ControlZeroClient
13
+ from control_zero.integrations.langchain import (
14
+ GovernedTool,
15
+ GovernedAgent,
16
+ ControlZeroCallbackHandler,
17
+ )
18
+
19
+ # Initialize Control Zero
20
+ client = ControlZeroClient(api_key="...")
21
+ client.initialize()
22
+
23
+ # Wrap tools with governance
24
+ governed_tool = GovernedTool(
25
+ tool=my_tool,
26
+ client=client,
27
+ )
28
+
29
+ # Use callback handler for comprehensive logging
30
+ callback = ControlZeroCallbackHandler(client)
31
+ agent.run("query", callbacks=[callback])
32
+ """
33
+
34
+ from control_zero.integrations.langchain.tool import GovernanceTool, GovernedTool
35
+ from control_zero.integrations.langchain.agent import GovernedAgent
36
+ from control_zero.integrations.langchain.chain import GovernedChain
37
+ from control_zero.integrations.langchain.callbacks import ControlZeroCallbackHandler
38
+ from control_zero.integrations.langchain.graph import (
39
+ GovernedNode,
40
+ governed_graph,
41
+ GovernedStateGraph,
42
+ )
43
+
44
+ __all__ = [
45
+ # Tools
46
+ "GovernanceTool",
47
+ "GovernedTool",
48
+ # Agents
49
+ "GovernedAgent",
50
+ # Chains
51
+ "GovernedChain",
52
+ # Callbacks
53
+ "ControlZeroCallbackHandler",
54
+ # LangGraph
55
+ "GovernedNode",
56
+ "governed_graph",
57
+ "GovernedStateGraph",
58
+ ]