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,311 @@
1
+ """
2
+ Governed LangChain Agent wrapper.
3
+
4
+ Provides governance enforcement at the agent level including:
5
+ - Per-action policy checks
6
+ - Cost and token tracking
7
+ - Conversation audit logging
8
+ - Tool filtering
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import time
14
+ from typing import Any, Dict, List, Optional, Sequence, Union
15
+
16
+ try:
17
+ from langchain.agents import AgentExecutor
18
+ from langchain_core.agents import AgentAction, AgentFinish
19
+ from langchain_core.tools import BaseTool
20
+ from langchain_core.callbacks import BaseCallbackHandler
21
+ LANGCHAIN_AVAILABLE = True
22
+ except ImportError:
23
+ LANGCHAIN_AVAILABLE = False
24
+ class AgentExecutor:
25
+ pass
26
+ class AgentAction:
27
+ pass
28
+ class AgentFinish:
29
+ pass
30
+ class BaseTool:
31
+ pass
32
+ class BaseCallbackHandler:
33
+ pass
34
+
35
+ from control_zero.client import ControlZeroClient
36
+ from control_zero.policy import PolicyDecision, PolicyDeniedError
37
+ from control_zero.integrations.langchain.tool import GovernanceTool
38
+
39
+
40
+ class GovernedAgent:
41
+ """
42
+ Governance wrapper for LangChain agents.
43
+
44
+ Wraps an AgentExecutor to provide:
45
+ - Tool-level policy enforcement
46
+ - Agent action logging
47
+ - Cost tracking
48
+ - Iteration limits
49
+
50
+ Usage:
51
+ from langchain.agents import initialize_agent
52
+
53
+ agent = initialize_agent(tools, llm, agent=AgentType.OPENAI_FUNCTIONS)
54
+
55
+ governed_agent = GovernedAgent(
56
+ agent=agent,
57
+ client=client,
58
+ max_iterations=10,
59
+ )
60
+
61
+ result = governed_agent.run("What's the weather?")
62
+ """
63
+
64
+ def __init__(
65
+ self,
66
+ agent: AgentExecutor,
67
+ client: ControlZeroClient,
68
+ max_iterations: Optional[int] = None,
69
+ max_execution_time: Optional[float] = None,
70
+ wrap_tools: bool = True,
71
+ agent_name: str = "langchain_agent",
72
+ ):
73
+ """
74
+ Initialize governed agent.
75
+
76
+ Args:
77
+ agent: The LangChain AgentExecutor to wrap
78
+ client: Control Zero client
79
+ max_iterations: Maximum agent iterations (overrides agent setting)
80
+ max_execution_time: Maximum execution time in seconds
81
+ wrap_tools: Whether to wrap agent tools with governance
82
+ agent_name: Name for logging purposes
83
+ """
84
+ if not LANGCHAIN_AVAILABLE:
85
+ raise ImportError(
86
+ "langchain is required. Install with: pip install langchain"
87
+ )
88
+
89
+ self._agent = agent
90
+ self._client = client
91
+ self._agent_name = agent_name
92
+ self._max_iterations = max_iterations
93
+ self._max_execution_time = max_execution_time
94
+
95
+ # Apply iteration limit
96
+ if max_iterations:
97
+ self._agent.max_iterations = max_iterations
98
+ if max_execution_time:
99
+ self._agent.max_execution_time = max_execution_time
100
+
101
+ # Wrap tools with governance if requested
102
+ if wrap_tools:
103
+ self._wrap_agent_tools()
104
+
105
+ # Tracking
106
+ self._total_iterations = 0
107
+ self._total_tool_calls = 0
108
+ self._session_start = time.time()
109
+
110
+ def _wrap_agent_tools(self) -> None:
111
+ """Wrap all agent tools with governance."""
112
+ governed_tools = []
113
+ for tool in self._agent.tools:
114
+ if isinstance(tool, GovernanceTool):
115
+ governed_tools.append(tool)
116
+ else:
117
+ governed_tools.append(
118
+ GovernanceTool(
119
+ base_tool=tool,
120
+ client=self._client,
121
+ )
122
+ )
123
+ self._agent.tools = governed_tools
124
+
125
+ def _check_agent_policy(self, action: str) -> PolicyDecision:
126
+ """Check policy for agent action."""
127
+ if hasattr(self._client, '_policy_cache') and self._client._policy_cache:
128
+ return self._client._policy_cache.evaluate(self._agent_name, action)
129
+ return PolicyDecision(effect="allow")
130
+
131
+ def run(
132
+ self,
133
+ input: Union[str, Dict[str, Any]],
134
+ callbacks: Optional[List[BaseCallbackHandler]] = None,
135
+ **kwargs,
136
+ ) -> str:
137
+ """
138
+ Run the agent with governance.
139
+
140
+ Args:
141
+ input: Agent input (string or dict)
142
+ callbacks: Additional callbacks
143
+ **kwargs: Additional arguments passed to agent
144
+
145
+ Returns:
146
+ Agent response string
147
+
148
+ Raises:
149
+ PolicyDeniedError: If agent execution is denied
150
+ """
151
+ # Check agent-level policy
152
+ decision = self._check_agent_policy("run")
153
+ if decision.effect == "deny":
154
+ self._log_denied("run", decision)
155
+ raise PolicyDeniedError(decision)
156
+
157
+ start = time.perf_counter()
158
+
159
+ try:
160
+ # Run agent
161
+ result = self._agent.run(input, callbacks=callbacks, **kwargs)
162
+
163
+ # Log success
164
+ latency_ms = int((time.perf_counter() - start) * 1000)
165
+ self._log_success("run", latency_ms, decision, result)
166
+
167
+ return result
168
+
169
+ except PolicyDeniedError:
170
+ raise
171
+ except Exception as e:
172
+ latency_ms = int((time.perf_counter() - start) * 1000)
173
+ self._log_error("run", latency_ms, decision, e)
174
+ raise
175
+
176
+ async def arun(
177
+ self,
178
+ input: Union[str, Dict[str, Any]],
179
+ callbacks: Optional[List[BaseCallbackHandler]] = None,
180
+ **kwargs,
181
+ ) -> str:
182
+ """Async run with governance."""
183
+ decision = self._check_agent_policy("run")
184
+ if decision.effect == "deny":
185
+ self._log_denied("run", decision)
186
+ raise PolicyDeniedError(decision)
187
+
188
+ start = time.perf_counter()
189
+
190
+ try:
191
+ result = await self._agent.arun(input, callbacks=callbacks, **kwargs)
192
+ latency_ms = int((time.perf_counter() - start) * 1000)
193
+ self._log_success("run", latency_ms, decision, result)
194
+ return result
195
+
196
+ except PolicyDeniedError:
197
+ raise
198
+ except Exception as e:
199
+ latency_ms = int((time.perf_counter() - start) * 1000)
200
+ self._log_error("run", latency_ms, decision, e)
201
+ raise
202
+
203
+ def invoke(
204
+ self,
205
+ input: Union[str, Dict[str, Any]],
206
+ config: Optional[Dict] = None,
207
+ **kwargs,
208
+ ) -> Dict[str, Any]:
209
+ """
210
+ Invoke the agent (LCEL interface) with governance.
211
+
212
+ Args:
213
+ input: Agent input
214
+ config: Run configuration
215
+ **kwargs: Additional arguments
216
+
217
+ Returns:
218
+ Agent output dict
219
+ """
220
+ decision = self._check_agent_policy("invoke")
221
+ if decision.effect == "deny":
222
+ self._log_denied("invoke", decision)
223
+ raise PolicyDeniedError(decision)
224
+
225
+ start = time.perf_counter()
226
+
227
+ try:
228
+ result = self._agent.invoke(input, config=config, **kwargs)
229
+ latency_ms = int((time.perf_counter() - start) * 1000)
230
+ self._log_success("invoke", latency_ms, decision, str(result))
231
+ return result
232
+
233
+ except PolicyDeniedError:
234
+ raise
235
+ except Exception as e:
236
+ latency_ms = int((time.perf_counter() - start) * 1000)
237
+ self._log_error("invoke", latency_ms, decision, e)
238
+ raise
239
+
240
+ async def ainvoke(
241
+ self,
242
+ input: Union[str, Dict[str, Any]],
243
+ config: Optional[Dict] = None,
244
+ **kwargs,
245
+ ) -> Dict[str, Any]:
246
+ """Async invoke with governance."""
247
+ decision = self._check_agent_policy("invoke")
248
+ if decision.effect == "deny":
249
+ self._log_denied("invoke", decision)
250
+ raise PolicyDeniedError(decision)
251
+
252
+ start = time.perf_counter()
253
+
254
+ try:
255
+ result = await self._agent.ainvoke(input, config=config, **kwargs)
256
+ latency_ms = int((time.perf_counter() - start) * 1000)
257
+ self._log_success("invoke", latency_ms, decision, str(result))
258
+ return result
259
+
260
+ except PolicyDeniedError:
261
+ raise
262
+ except Exception as e:
263
+ latency_ms = int((time.perf_counter() - start) * 1000)
264
+ self._log_error("invoke", latency_ms, decision, e)
265
+ raise
266
+
267
+ def _log_denied(self, method: str, decision: PolicyDecision) -> None:
268
+ """Log denied execution."""
269
+ self._client._log(
270
+ self._agent_name, method, "denied", 0,
271
+ policy_decision=decision
272
+ )
273
+
274
+ def _log_success(
275
+ self,
276
+ method: str,
277
+ latency_ms: int,
278
+ decision: PolicyDecision,
279
+ result: str,
280
+ ) -> None:
281
+ """Log successful execution."""
282
+ self._client._log(
283
+ self._agent_name, method, "success", latency_ms,
284
+ policy_decision=decision,
285
+ metadata={"result_length": len(result)}
286
+ )
287
+
288
+ def _log_error(
289
+ self,
290
+ method: str,
291
+ latency_ms: int,
292
+ decision: PolicyDecision,
293
+ error: Exception,
294
+ ) -> None:
295
+ """Log execution error."""
296
+ self._client._log(
297
+ self._agent_name, method, "error", latency_ms,
298
+ policy_decision=decision,
299
+ error_type=type(error).__name__,
300
+ error_message=str(error)
301
+ )
302
+
303
+ @property
304
+ def tools(self) -> List[BaseTool]:
305
+ """Get agent tools."""
306
+ return self._agent.tools
307
+
308
+ @property
309
+ def agent(self) -> AgentExecutor:
310
+ """Get underlying agent executor."""
311
+ return self._agent